1 vài thủ thuật tăng performence trong AngularJS

Giới thiệu</br> Mặc dù mới làm quen với AngularJS nhưng bài trong bài viết này,tôi xin giới thiệu với các bạn 1 vài thủ thuật mà tôi đã tham khảo và tích lũy từ nhiều nguồn trong vc tăng performence của AngularJS

  1. Giảm tối đa hoặc tránh sử dụng các Watchers</br> Thông thường, nếu ứng dụng AngularJS của bạn chậm, có nghĩa là bạn có quá nhiều watcher, hoặc những watcher đang làm việc quá nhiều.</br> AngularJS sử dụng dirty checking để theo dõi tất cả những thay đổi trong ứng dụng. Điều này có nghĩa là nó sẽ phải đi qua tất cả các watcher để kiểm tra xem họ cần phải được cập nhật (gọi chu trình tiêu hóa). Nếu một trong những watcher được tin cậy bởi watcher khác, AngularJS sẽ phải chạy lại chu trình tiêu hóa một lần nữa, để đảm bảo rằng tất cả các thay đổi được truyền đi. Nó sẽ tiếp tục làm như vậy, cho đến khi tất cả các watcher đã được cập nhật và ứng dụng đã ổn định.</br> Mặc dù chạy JavaScript trong trình duyệt hiện đại thực sự là nhanh, trong Angular nó khá dễ dàng để thêm rất nhiều watcher ,tuy nhiên nó sẽ làm ứng dụng chậm như rùa bò.</br> Watcher được khai báo theo nhiều cách như:</br>

    • scope.$watch
    • {{}} bindings
    • directives (ví dụ ng-show)
    • Scope variables scope: { bar: '='}
    • Filters {{ value | myFilter }}
    • ng-repeat

    Watchers (digest cycle) chạy trên các hàm như :

    • Các hàm đc người dùng định nghĩa (ví dụ như ng-click). và các thành phần đc build trong các directives sẽ gọi $scope.apply để hoàn thành triggers của digest cycle.
    • ng-change
    • ng-model
    • $http events (tất cả các ajax calls)
    • $q promises
    • $timeout
    • $interval
    • $scope.apply và $scope.digest
  2. Tránh sử dụng ng-repeat. Trong trường hợp bắt buộc phải dùng ng-repeat thì có thể xem xét đổi qua sử dụng infinite scrolling hoặc pagination</br>

    app.directive('infiniteScroll', [
      '$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) {
        return {
          link: function(scope, elem, attrs) {
            var checkWhenEnabled, handler, scrollDistance, scrollEnabled;
            $window = angular.element($window);
            elem.css('overflow-y', 'scroll');
            elem.css('overflow-x', 'hidden');
            elem.css('height', 'inherit');
            scrollDistance = 0;
            if (attrs.infiniteScrollDistance != null) {
              scope.$watch(attrs.infiniteScrollDistance, function(value) {
                return scrollDistance = parseInt(value, 10);
              });
            }
            scrollEnabled = true;
            checkWhenEnabled = false;
            if (attrs.infiniteScrollDisabled != null) {
              scope.$watch(attrs.infiniteScrollDisabled, function(value) {
                scrollEnabled = !value;
                if (scrollEnabled && checkWhenEnabled) {
                  checkWhenEnabled = false;
                  return handler();
                }
              });
            }
            $rootScope.$on('refreshStart', function(event, parameters){
                elem.animate({ scrollTop: "0" });
            });
            handler = function() {
              var container, elementBottom, remaining, shouldScroll, containerBottom;
              container = $(elem.children()[0]);
              elementBottom = elem.offset().top + elem.height();
              containerBottom = container.offset().top + container.height();
              remaining = containerBottom - elementBottom ;
              shouldScroll = remaining <= elem.height() * scrollDistance;
              if (shouldScroll && scrollEnabled) {
                if ($rootScope.$$phase) {
                  return scope.$eval(attrs.infiniteScroll);
                } else {
                  return scope.$apply(attrs.infiniteScroll);
                }
              } else if (shouldScroll) {
                return checkWhenEnabled = true;
              }
            };
            elem.on('scroll', handler);
            scope.$on('$destroy', function() {
              return $window.off('scroll', handler);
            });
            return $timeout((function() {
              if (attrs.infiniteScrollImmediateCheck) {
                if (scope.$eval(attrs.infiniteScrollImmediateCheck)) {
                  return handler();
                }
              } else {
                return handler();
              }
            }), 0);
          }
        };
      }
    ]);
  1. Sử dụng Oneway Binding</br>
    <head>
      <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.0-rc.0/angular.min.js"></script>
    </head>
    <body ng-app="oneTimeBidingExampleApp">
      <div ng-controller="EventController">
      <button ng-click="clickMe($event)">Click Me</button>
      <!-- WILL ONLY BIND ONCE -->
      <p id="one-time-binding-example">One time binding: {{::name}}</p>

      <!-- WILL UPDATE EVERY TIME BUTTON IS CLICKED -->
      <p id="normal-binding-example">Normal binding: {{name}}</p>

    </div>
    </body>
    (function(angular) {
      'use strict';
      angular.module('oneTimeBidingExampleApp', []).
        controller('EventController', ['$scope', function($scope) {
          var counter = 0;
          var names = ['Igor', 'Misko', 'Chirayu', 'Lucas'];

          $scope.clickMe = function(clickEvent) {
            $scope.name = names[counter % names.length];
            counter++;
          };
        }]);
    })(window.angular);
  1. Sử dụng $watchCollection thay cho $watch</br> $watch với 2 parameters có tốc độ khá là nhanh. Tuy nhiên, Angular với 3 tham số $watch('value', function(){}, true) sẽ giúp cho việc quản lý tài nguyên đc sâu và kĩ càng hơn,tuy nhiên đánh đổi lại là làm cho performence giảm đi khá nhiều</br> Để Giải quyết vấn đề này, angular đã thêm $watchCollection('value', function(){}). $watchColleciton hoạt động giống với $watch với 3 tham số, tuy nhiên nó chỉ kiểm tra phần tử đầu tiên của object, việc này cải thiện khá nhiều cho vấn đề tăng tốc độ</br>
    var module = angular.module("MyModule", []);
    module.controller("MyController", function($scope, $log, $timeout) {
        $scope.$watch("myArray", function() {
            $log.debug("    ** $watch()");
        });

        $scope.$watch("myArray", function() {
            $log.debug("    ** $watch(..., true)");
        }, true);

        $scope.$watchCollection("myArray", function() {
            $log.debug("    ** $watchCollection()");
        });

        $log.debug("So it begins...");

        setTimeout(function() {
            $log.debug("$scope.myArray = null // direct assignment");
            $scope.myArray = null;
            $scope.$digest();

            $log.debug("$scope.myArray = [] // direct assignment");
            $scope.myArray = [];
            $scope.$digest();

            $log.debug("$scope.myArray = [] // direct assignment again - same content");
            $scope.myArray = [];
            $scope.$digest();

            $log.debug("$scope.myArray.push({ name: 'John', skill: 'Wizard' }) // add element");
            $scope.myArray.push({ name: 'John', skill: 'Wizard' });
            $scope.$digest();

            $log.debug("$scope.myArray.push({ name: 'David', skill: 'Mage' }) // add element");
            $scope.myArray.push({ name: 'David', skill: 'Mage' });
            $scope.$digest();

            $log.debug("$scope.myArray.splice(0, 1) // remove element");
            $scope.myArray.splice(0, 1);
            $scope.$digest();

            $log.debug("$scope.myArray[0] = { name: 'Edgar', skill: 'Warrior' } // element assignment");
            $scope.myArray[0] = { name: 'Edgar', skill: 'Warrior' };
            $scope.$digest();

            $log.debug("$scope.myArray[0].skill = 'Software Developer' // assign element property");
            $scope.myArray[0].skill = 'Software Developer';
            $scope.$digest();
        });
    });
  1. Tránh lặp đi lặp lại và cache bất cứ khi nào có thể</br> Tôi nghĩ code sẽ rõ ràng và dễ hiểu hơn khi gán các biến ở trong ứng dụng,tuy nhiên việc lặp đi lặp lại các biến hay filter trong ứng dụng cũng có thể làm ảnh hưởng tới tốc độ của ứng dụng.</br> Thay vì sử dụng đi sử dụng lại đoạn code sau:</br>
    {{'DESCRIPTION' | translate }}
Ta có thể thay bằng cách sau:</br>
    $scope.description: $translate.instant('DESCRIPTION')
    {{::description}}