Search code examples
javascriptangularjsasp.net-web-api2asp.net-mvc-5.1

How to Post multiple rows with AngularJS


I am trying to submit a form with multiple table rows. I have found examples online and have managed to send the table data to the AngularJS controller but I cannot figure out how to send that data to the apiController.

The form is a Purchase Order that has a table with the Purchase Order Details. I have chained the submit button with the both the Purchase Order and Purchase Order Detail submit functions.

<table class="table" style="">
    <tbody>
        <tr class="pointer no_selection" ng-repeat="newJobItem in rows">
            <td style="width:50px"><input style="width:20px" type="checkbox" class="form-control"></td>
            <td style="width:200px">{{newJobItem.JobItemName}}</td>
            <td style="width:480px">{{newJobItem.JobItemDescription}}</td>
            <td style="width:100px">{{newJobItem.JobItemMatSize}}</td>
            <td style="width:150px">{{newJobItem.JobItemQuantity}}</td>
            <td style="width:50px">{{newJobItem.JobItemUOM}}</td>
            <td style="width:150px">${{newJobItem.JobItemPrice | number : fractionSize}}</td>
            <td style="width:20px"><input type="button" value="X" class="btn btn-primary btn-sm" ng-click="removeRow(newJobItem.JobItemName)" /></td>
        </tr>

    </tbody>
</table>


<input style="margin-right:30px" id="btn-width" type="button" class="btn btn-default" ng-click="submitPurchaseOrder();submitPurchaseOrderDetail()" value="Submit"/>

Controller

   //Post Purchase Order
$scope.PODate = new Date(); //Todays Date
$scope.POId = Math.floor(Math.random() * 1000000001) //PurchaseOrder Id Generator
$scope.submitPurchaseOrder = function () {;
    var data = {
        JobId: $scope.job.JobId,
        POId : $scope.POId,
        PONumber: $scope.currentItem.PONumber,
        PODate: $scope.PODate,
        POAmount: $scope.currentItem.POAmount,
        POLastPrintDate: $scope.currentItem.POLastPrintDate,
        POEmail: $scope.POEmail,
        POPrint: $scope.currentItem.POPrint,
        POFaxNumber: $scope.POFaxNumber,
        PONotes: $scope.currentItem.PONotes,
        POCreatedBy: $scope.currentItem.POCreatedBy,
        PODeliveryDate: $scope.currentItem.PODeliveryDate,
        POShipVia: $scope.currentItem.POShipVia,
        POShowPrices: $scope.currentItem.POShowPrices,
        POCostCode: $scope.currentItem.POCostCode,
        POApprovedNumber: $scope.currentItem.POApprovedNumber,
        POBackorder: $scope.currentItem.POBackorder,
       }
    $http.post('/api/apiPurchaseOrder/PostNewPurchaseOrder', data).success(function (data, status, headers) {
        console.log(data);
        var tmpCurrentItem = angular.copy($scope.currentItem);
        $scope.purchaseOrderArray.push(tmpCurrentItem)
        angular.copy({}, $scope.currentItem);
        //hide modal window
        $scope.openNewPurchaseOrderModal.then(function (m) {
            m.modal('hide');
        });

    });
};
//Post Purchase Order Detail
$scope.newJobItem = {};
$scope.submitPurchaseOrderDetail = function () {
    var index = 0;
    $scope.rows.forEach(function (newJobItem) {
        console.log('rows #' + (index++) + ': ' + JSON.stringify(newJobItem));
    });
    var data = {
        POId: $scope.POId,
        PODItem: $scope.newJobItem.JobItemName,
        PODDescription: $scope.newJobItem.JobItemDescription,
        PODNotes: $scope.PODNotes,
        PODUOM: $scope.newJobItem.JobItemUOM,
        PODPrice: $scope.newJobItem.JobItemPrice,
        PODQuantity: $scope.newJobItem.JobItemQuantity,
        PODAmount: $scope.PODAmount,
        PODMatSize: $scope.newJobItem.JobItemMatSize,
        PODSection: $scope.PODSection,
        PODMultiplier: $scope.PODMultiplier,
        PODBackOrder: $scope.PODBackOrder
    }
    $http.post('/api/apiPurchaseOrderDetail/PostNewPurchaseOrderDetail', data).success(function (data, status, headers) {
        console.log(data); window.top.location.reload();

    });
};

Purchase Order Detail ApiController

 // POST api/<controller>

    public async Task<IHttpActionResult> PostnewPurchaseOrderDetail([FromBody]PurchaseOrderDetail newPurchaseOrderDetail)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        using (var context = new ApplicationDbContext())
        {
            context.PurchaseOrderDetails.Add(newPurchaseOrderDetail);
            await context.SaveChangesAsync();
            return CreatedAtRoute("PurchaseOrderDetailApi", new { newPurchaseOrderDetail.PODId }, newPurchaseOrderDetail);
        }
    }

Update Changed as suggested

 // POST api/<controller>
    public HttpResponseMessage PostNewPurchaseOrderDetail(int id, PurchaseOrderDetail newPurchaseOrderDetail)
    {
        ApplicationDbContext db = new ApplicationDbContext();
        if (!ModelState.IsValid)
        {
            return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
        }

        if (id != newPurchaseOrderDetail.PODId)
        {
            return Request.CreateResponse(HttpStatusCode.BadRequest);
        }

        db.Entry(newPurchaseOrderDetail).State = EntityState.Modified;

        try
        {
            db.SaveChanges();
        }
        catch (DbUpdateConcurrencyException ex)
        {
            return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
        }

        return Request.CreateResponse(HttpStatusCode.OK);
    }

Solution

  • You are executing two functions, each making an asyncronous request:

    ng-click="submitPurchaseOrder();submitPurchaseOrderDetail()"
    

    This doesn't feel right - both requests are sent in parallel and none is waiting for the other. Do you really mean it?

    I would either package and send all data in a single request (also better experience for the user), then let the server deal with un-packaging. Or, if one request needs to wait for another, chain the Promises returned by $http:

    $http.post(...)
         .then(function(){
             return $http.post(...);
         })
         .success(...)
         .fail(...);
    

    or use $q.all(promises) instead.

    EDIT.

    Also a cleaner and more scalable approach is to use dedicated Angular Service to post your data, see e.g. example on Angular Homepage