Ways to improve AngularJS performance even with large number of DOM elements

19,948

Solution 1

I think that the best way to solve performance issues is to avoid using high level abstractions (AngularJS ng-repeat with all corresponding background magic) in such situations. AngularJS is not a silver bullet and it's perfectly working with low level libraries. If you like such functionality in a text block, you can create a directive, which will be container for text and incapsulate all low level logic. Example with custom directive, which uses letteringjs jquery plugin:

angular.module('myApp', [])
  .directive('highlightZone', function () {
    return {
      restrict: 'C',
      transclude: true,
      template: '<div ng-transclude></div>',
      link: function (scope, element) {
        $(element).lettering('words')
      }
    }
  })

http://jsfiddle.net/j6DkW/1/

Solution 2

Although an accepted answer exists allready, I think its important to understand why angularJS reacts so slow at the code you provided. Actually angularJS isnt slow with lots of DOM elements, in this case it's slow because of the ng-mouseover directive you register on each item in your list. The ng-mouseover directive register an onmouseover event listener, and every time the listener function gets fired, an ng.$apply() gets executed which runs the $diggest dirty comparision check and walks over all watches and bindings.

In short words: each time an element gets hovered, you might consume e.g. 1-6 ms for the internal
angular dirty comparision check (depending on the count of bindings, you have established). Not good :)

Thats the related angularJS implementation:

var ngEventDirectives = {};
    forEach('click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
        function(name) {
            var directiveName = directiveNormalize('ng-' + name);
            ngEventDirectives[directiveName] = ['$parse', function($parse) {
            return {
                compile: function($element, attr) {
                    var fn = $parse(attr[directiveName]);
                    return function(scope, element, attr) {
                        element.on(lowercase(name), function(event) {
                            scope.$apply(function() {
                                fn(scope, {$event:event});
                            });
                        });
                    };
                }
            };
       }];
    }
);

In fact, for highlighting a hovered text, you probably would use CSS merely:

.list-item:hover {
    background-color: yellow;
}

It is likely that with newer Angular Versions, your code as is, will run significantly faster. For angular version 1.3 there is the bind-once operator :: which will exclude once-binded variables from the digest loop. Having thousands of items, exluded will reduce the digest load significantly.

As with ECMAScript 6, angular can use the Observe class, which will make the dirty comparisiion check totaly obsolete. So a single mouseover would result internally in a single event callback, no more apply or diggesting. All with the original code. When Angular will apply this, I dont know. I guess in 2.0.

Solution 3

This is an old question now, but I think it's worth adding to the mix that Angular (since v1.3) now supports one time binding which helps slim down the digest loop. I have worked on a few applications where adding one time binding reduced the number of watches dramatically which led to improved performance. ng-repeat is often responsible for adding a lot of watches, so you could potentially consider adding one time binding to the ng-repeat.

ng-repeat="i in ::list"

Here is a summary of a few techniques that can be used to avoid adding unnecessary watches

http://www.syntaxsuccess.com/viewarticle/547a8ba2c26c307c614c715e

Solution 4

Always profile first to find the real bottleneck. Sometimes it might be not something you initially suspect. I would suspect your own code first, then Angular (high number of watchers being the main feature leading to sluggish performance). I described how to profile and solve different performance problems in an Angular app in detailed blog post https://glebbahmutov.com/blog/improving-angular-web-app-performance-example/

Share:
19,948

Related videos on Youtube

Steven
Author by

Steven

Updated on September 15, 2022

Comments

  • Steven
    Steven over 1 year

    I'm evaluating whether or not to use AngularJS for a web project, and I'm worried about the performance for a feature I need to implement. I would like to know if there's a better way to implement the functionality I'm trying to in AngularJS.

    Essentially, it seems to me the time it takes AngularJS to react to an event is dependent on the number of DOM elements in the page, even when the DOM elements aren't being actively changed, etc. I'm guessing this is because the $digest function is traversing the entire DOM.. at least from my experiments, that seems to be the case.

    Here's the play scenario (this isn't exactly what I'm really trying to do, but close enough for testing purposes).

    I would like to have angularJS highlight a word as I hover over it. However, as the number of words in the page increases, there's a larger delay between when you hover over the word and when it is actually highlighted.

    The jsfiddle that shows this: http://jsfiddle.net/czerwin/5qFzg/4/

    (Credit: this code is based on a post from Peter Bacon Darwin on the AngularJS forum).

    Here's the HTML:

    <div ng-app="myApp">
        <div ng-controller="ControllerA">
            <div >
                <span ng-repeat="i in list" id="{{i}}" ng-mouseover='onMouseover(i)'>
                    {{i}}, 
                </span>
                <span ng-repeat="i in listB">
                    {{i}}, 
                </span>
            </div>
        </div>
    </div>
    

    Here's the javascript:

    angular.module('myApp', [])
    .controller('ControllerA', function($scope) {
        var i;
        $scope.list = [];
        for (i = 0; i < 500; i++) {
            $scope.list.push(i);
        }
    
        $scope.listB = [];
        for (i = 500; i < 10000; i++) {
            $scope.listB.push(i);
        }
    
        $scope.highlightedItem = 0;
        $scope.onMouseover = function(i) {
            $scope.highlightedItem = i;
        };
    
        $scope.$watch('highlightedItem', function(n, o) {
            $("#" + o).removeClass("highlight");
            $("#" + n).addClass("highlight");
        });
    });
    

    Things to note: - Yes, I'm using jquery to do the DOM manipulation. I went this route because it was a way to register one watcher. If I do it purely in angularJS, I would have to register a mouseover handler for each span, and that seemed to make the page slow as well. - I implemented this approach in pure jquery as well, and the performance was fine. I don't believe it's the jquery calls that are slowing me down here. - I only made the first 500 words to have id's and classes to verify that it's really just having more DOM elements that seems to slow them down (instead of DOM elements that could be affected by the operation).

    • ganaraj
      ganaraj almost 11 years
      can you point to the google group thread where peter is talking about this?
    • Steven
      Steven almost 11 years
      Here's the link: groups.google.com/forum/?fromgroups=#!searchin/angular/… In the post, he draws the distinction about the DOM elements being "data-bound" and therefore more costly when $digest is run. I thought that since my elements did not depend on values that were being changed in the scope, they wouldn't affect $digest running time. I now believe that's not right. I'm still investigating, but it looks like adding in the <span> elements using the ng-repeat loop leads to much difference performance versus if you just manually create them.
    • Mahesh
      Mahesh about 9 years
      Refer this stackoverflow question it will surely help you. [stackoverflow.com/questions/25481021/… [1]: stackoverflow.com/questions/25481021/…
  • Steven
    Steven almost 11 years
    Thanks Artem. Yes, after fooling with this more, I agree the the problem is with the ng-repeat mainly because it makes numerous watchers that all have to execute during digest. I found that if I worked to use a customer directive that did not add thousands of watcher functions, then the lag drastically improved.
  • Bernardo Dal Corno
    Bernardo Dal Corno over 6 years
    This is a good solution, but in this case the css processing made it real fast. It may not be aplicable elsewhere