Monday, September 26, 2016

[AngularJS] Sequence async requests in loop

 AngularJS    javascript    asyc callback   sequence request


Introduction


Though async calls (ajax) and callbacks are most useful way to implement front-end with javascript. We sometimes have to make the async function running as sequential function.
For example, we have an array with customers’ id, and we are going to get the customers’ information one by one from backend thru ajax.
There are several ways to complete this requirement, we can get the data async asynchronously and sort the results based on the order of the original array. However, in the following sample, I will try to implement it by sending sequence requests.

Environment

l   AngularJS 1.5.8




Implement


Goal

We have an array with the customers’ ids, and the final result will be displaying the customers’ information thru async inquiry function but in the same order of the original id array. Futhermore, we want to summarize all the cash they have AFTER all the customers’ information are responsed.


 


Step 1 : async calls  (Sample code)

In step1, we will ignore the order of the result and just shows the customers’ information thru async requests.

First, we simulate an async function for customer-information inquery with different response time.

JS - service
angular.module('app', [])
  .service('getCustomers', function ($q, $timeout) {
      var data = [{
          'id': '1',
          'name': 'JB',
          'phone': '0933XXXXXX',
          'cash': 100
      }, {
          'id': '2',
          'name': 'Lily',
          'phone': '0910YYYYYY',
          'cash': 200
      }, {
          'id': '3',
          'name': 'Leia',
          'phone': '0982ZZZZZZ',
          'cash': 300
      }, {
          'id': '4',
          'name': 'hachi',
          'phone': '0955ZZZZZZ',
          'cash': 400
      }];

      var getdata = function (id) {

          var deferred = $q.defer();
          try {
              var randomTimeout = getRandomInt(1000, 3000); //The response time will be during 1sec-3sec
              $timeout(function () {
                  for (var i = 0; i < data.length; i++) {
                      var item = data[i];
                      if (item.id === id) {
                          deferred.resolve(item);
                          break;
                      }
                  }

              }, randomTimeout);
          } catch (err) {
              deferred.reject(err);
          }

          return deferred.promise;
      }

      function getRandomInt(min, max) {
          return Math.floor(Math.random() * (max - min + 1)) + min;
      }

      return getdata;
  })

JS – controller
angular.module('app', [])
  .controller('DemoCtrl', function ($scope, $q, getCustomers) {
      $scope.customers = [];
      $scope.cashSum = 0;
      var searchIds = ['3', '4', '1', '2'];
      angular.forEach(searchIds, function (id) {

          var promise = getCustomers(id);
          promise.then((rtn) => {
              $scope.customers.push(rtn);
              $scope.cashSum += rtn.cash;
          });
      })
  })


HTML
<div ng-app='app' ng-controller='DemoCtrl'>
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Phone</th>
                <th>Cash</th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="cust in customers">
                <td>{{cust.id}}</td>
                <td>{{cust.name}}</td>
                <td>{{cust.phone}}</td>
                <td>{{cust.cash}}</td>
            </tr>
            <tr>
                <td colspan=3>總金額</td>
                <td>{{cashSum}}</td>
            <tr>
        </tbody>
        <tr>
    </table>
</div>

Okay, it’s done the final result will be like the following snapshot. The order of the results will depends on the resposne time of each async call and the cash summary is called after each response.

 


Step 2 : Summarize after all customers’ requests response (Sample code)

In this step, we will use $q.all to make sure that all the requests are back and then trigger the summarize callback.

JS - controller
angular.module('app', [])
  .controller('DemoCtrl', function ($scope, $q, $timeout, getCustomers) {
      $scope.customers = [];
      $scope.cashSum = 0;
      var searchIds = ['3', '4', '1', '2'];

      var promiseCollection = [];
      angular.forEach(searchIds, function (id) {

          var promise = getCustomers(id);
          promiseCollection.push(promise);

          promise.then((rtn) => {
              $scope.customers.push(rtn);
          });
      })

      $q.all(promiseCollection).then(() => {
              angular.forEach($scope.customers, function (cust) {
                  $scope.cashSum += cust.cash;
              })
      })

  })

Result :

 


Final step : Sequence reuquests (Sample code)

Here is the sample code of using recursive function for making sequential requests in a for-loop.

JS – recursive sample
var index = 0;
next();

function next() {
    if (index < searchIds.length) {
        var promise = getCustomers(searchIds[index]);
        promise.then((rtn) => {
            $scope.customers.push(rtn);
            index++;
            next();
        });
    }
}

This is a smart way by using recursive method and callback to make the request be sent one by one (sequentially).


Now we can use the above method to modify our js:controller.

JS – controller
angular.module('app', [])
  .controller('DemoCtrl', function ($scope, $q, $timeout, getCustomers, seqSearch) {
      $scope.customers = [];
      $scope.cashSum = 0;
      var searchIds = ['3', '4', '1', '2'];

      var deferred = $q.defer();
      var index = 0;
      next();

      function next() {
          if (index < searchIds.length) {
              var promise = getCustomers(searchIds[index]);
              promise.then((rtn) => {
                  $scope.customers.push(rtn);
                  index++;
                  next();
              });
          }
          else {
              deferred.resolve();
          }
      }

      deferred.promise.then(() => {
              angular.forEach($scope.customers, function (cust) {
                  $scope.cashSum += cust.cash;
              })
      })

  })

Notice that we don’t need to use $q.all to wait all the customers’ information requests because they are now sequence requests. Instead, we use a deferred ($q.defer) to promise that the recursive function is finished and then summarize the cash.

Final result is as following. The order of the result now is based on the order of the customers’ id array. Cheeerrrrs!


 




Reference



Monday, September 12, 2016

[AngularJS] $emit and $on

 AngularJS    emit    on  


Introduction


Similar to $broadcast, $emit is using for broadcasting an event. However, the difference between them is that $broadcast broadcasts parent’s event to all child scopes, however, $emit does the opposite way and broadcasts the child’s event to all of its parent scopes.

In this sample, I am going to modify the original sample codes of $broadcast and make the
Parent’s checkboxes be checked while the ones are checked in directive.


Environment

n   AngularJS 1.5.8




Implement


Html

<div ng-app="app" ng-controller="AppCtrl">
    <div>
        <table class="list">
            <tr ng-repeat="item in Customers" ng-class="item.customClass">
                <td><input type="checkbox" ng-checked="item.IsChecked" ng-click="setChecked(item)"></td>
                <td><label class="control-label">{{item.Name}}</label></td>
            </tr>
        </table>
    </div>
    <hr />
    <div ng-repeat="item in Customers">
        <angu-customer ng-model="item" id="{{item.Id}}" />
    </div>
</div>



Directive

angular.module('app', [])
  .directive('anguCustomer', function ($http, $q) {

      var templatehtml = '<table class="table">' +
        '<tr><td><input type="checkbox" ng-checked="Customer.IsChecked" ng-click="setChecked(Customer)"/></td><td>' +
        '<input type="text" ng-model="Customer.Name" value="{{Customer.Name}}" />' +
        '</td><td>' +
        '<input type="text" ng-model="Customer.Phone" value="{{Customer.Phone}}" />' +
        '</td></tr></table>';

      return {
          //restrict: "A",
          scope: {
              Customer: "=ngModel",
              id: "@"
          },
          template: templatehtml,
          link: function ($scope, $element) {

              $scope.setChecked = function (customer) {
                  customer.IsChecked = !customer.IsChecked;
                  $scope.$emit('customer:setChecked', $scope.id, $scope.Customer.IsChecked);

              }

          },
          controller: function ($scope, $element) {

          }
      }
  })

n   We will use $emit to broadcast an event : “customer:setChecked”



Controller (Parent scope)


angular.module('app', [])
.controller('AppCtrl', function ($scope) {

      var scope = $scope;

      scope.Customers = [{
          'Id': 'C1',
          'Name': 'JB',
          'Phone': '0933XXXXXX',
          'IsChecked': false
      }, {
          'Id': 'C2',
          'Name': 'Lily',
          'Phone': '0910YYYYYY',
          'IsChecked': false
      }]

      scope.$on('customer:setChecked', function (event, elementId, value) {
          angular.forEach(scope.Customers, function (cust) {
              if (cust.Id === elementId) {
                  if (value == true) {
                      cust.customClass = "highlight";
                      cust.IsChecked = true;
                  } else {
                      cust.customClass = "";
                      cust.IsChecked = false;
                  }
              }
          })
      });
})

n   Use $on to listen for the event from child scope.







Reference


Tuesday, September 6, 2016

[AngularJS] Pass function to directive

 AngularJS    Directive    Function parameter  


Introduction


Since we had learned how to create directive, we can pass parameters from parent scope to directive. The parameters could be values, ngModels or functions. This article will show how to pass parent’s function to directive, which results in the possibility for using the parent’s function in directive.


Environment

l   AngularJS 1.5.5




Implement


What we will do

We are going to initialize an integer array and create a totaling function in parent scope. Then we will pass the array and function to a directive and the directive will list the integers and calculate the totaling number with the totaling function.



Controller

angular.module('app', [])
.controller('MainCtrl', function ($scope) {
    $scope.numbers = [10, 20, 30, 40, 50];
    $scope.calculate = function (numbers) {
        var total = 0;
        angular.forEach(numbers, function (num) {
            total += num;
        });
        return total;
    }

})



Html

<div ng-app="app" ng-controller="MainCtrl">
    <div my-directive
         title="Math calculator"
         data="numbers"
         calculate-func="calculate"></div>
</div>




Directive

angular.directive('myDirective', function(){
    var template = '<h3>{{title}}</h3><hr />' +
         '<table class="table"><thead><tr>'+
                   '<td>SN</td><td>Number</td>' +
                   '</tr></thead>'+
                   '<tbody>'+
                   '<tr ng-repeat="num in data"><td>{{$index}}</td><td>{{num}}</td></tr>' +
                   '<tr><td>Total</td><td>{{total}}</td></tr>'
    '</tbody>'+
    '</table>';
    return {
   
        scope: {
            title: "@",
            data: "=",
            calculateFunc: "&"
            //calculateFunc: "="
        },
        template : template,
        link: function($scope, $element, $attr){
            $scope.total = 0;
            $scope.total = $scope.calculateFunc()($scope.data);
            //$scope.total = $scope.calculateFunc($scope.data);
        },
        controller: function($scope, $element){
        }
    }
})

There are two ways for passing the parent’s function:


parameter
usage
1
calculateFunc: ‘&
$scope.calculateFunc()(inputData);
2
calculateFunc: ‘=
$scope.calculateFunc(inputData);






Reference