1

From what I have seen with Angular 2.0, I have a feeling I am going to be using Angular 1.x for a while. It has all the building blocks that I think I need, the only downside is that it does have performance issue with dirty checking so I am trying to think about that more. Now ng-repeat can be an issue because of the number of watchers it adds.

So I have this part of a template (in jade):

li(ng-repeat='topMenuItem in sideMenu.topMenu', ng-class='{"is-active": topMenuItem.display === sideMenu.activeTopMenu}')
  a(href='{{ topMenuItem.href }}') {{ topMenuItem.display }}
    ul(ng-if='sideMenu.secondMenu.length > 0 && topMenuItem.display === sideMenu.activeTopMenu')
      li(ng-repeat='secondMenuItem in sideMenu.secondMenu', ng-class='{"is-active": secondMenuItem.display === sideMenu.activeSecondMenu}')
        a(href='{{ secondMenuItem.href }}') {{ secondMenuItem.display }}

When this displays 22 menu items the number of watchers is 90 (using this bookmark snippet).

I decided to play around with trying to use $interpolate to generate that menu. I ended up with a directive with this for the compile function:

compile: function(element, attributes) {
  var topLevelExpression = $interpolate('<li{{ cssClass }}><a href="{{ topMenuItem.href }}">{{ topMenuItem.display }}</a>{{ secondLevelMenuHtml }}</li>');
  var secondLevelExpression = $interpolate('<li{{ cssClass }}><a href="{{ secondMenuItem.href }}">{{ secondMenuItem.display }}</a></li>');

  var updateMenu = function() {
    var html = '';

    sideMenu.topMenu.forEach(function(topMenuItem) {
      var cssClass = topMenuItem.display === sideMenu.activeTopMenu ? ' class="is-active"': '';
      var secondLevelMenuHtml = '';

      if(sideMenu.secondMenu.length > 0 && topMenuItem.display === sideMenu.activeTopMenu) {
        secondLevelMenuHtml += '<ul>';

        sideMenu.secondMenu.forEach(function(secondMenuItem) {

          var cssClass = secondMenuItem.display === sideMenu.activeSecondMenu ? ' class="is-active"': '';
          secondLevelMenuHtml += secondLevelExpression({
            secondMenuItem: secondMenuItem,
            cssClass: cssClass,
          });
        });

        secondLevelMenuHtml += '</ul>';
      }

      html += topLevelExpression({
        topMenuItem: topMenuItem,
        cssClass: cssClass,
        secondLevelMenuHtml: secondLevelMenuHtml
      });
    });

    element.find('.application-navigation').html(html);
  };

  return function($scope) {
    $scope.$watchCollection('sideMenu', function() {
      updateMenu();
    });
  }
}

From my testing, this code functions exactly the same as the ng-repeat, the output look as it should. This version only has 16 watchers and that number does not increase when more elements are shown where the ng-repeat does.

Since this code is only doing the bare minimum that is needed for this piece of code to work, I imagine the javascript itself is just as efficient (if not more efficient) than the code that executes for ng-repeat.

Is that assumption correct?

Are there any issues with doing looping DOM generation in this way vs using ng-repeat?

John Slegers
  • 45,213
  • 22
  • 199
  • 169
ryanzec
  • 27,284
  • 38
  • 112
  • 169
  • Have you tried [one-time binding](https://docs.angularjs.org/guide/expression#one-time-binding), e.g. `{{::secondMenuItem.hjef}}`? This should reduce the number of watchers – New Dev Oct 29 '14 at 23:14
  • The menu may change, but not the properties of the items. – a better oliver Nov 07 '14 at 11:13

1 Answers1

0

Tricky question.

I initially thought that if you one-time-bind the property of an object, then if the object changed, the properties would be re-bound.

<div>{{::menu.menuItem}}</div>
<button ng-click="changeMenu()">switch</div>

That is NOT the case.

What one could do though - albeit with a bit of a flaky/hacky approach (perhaps a better approach could be suggested in comments) - is to force Angular to re-bind. One such candidate is ng-if:

<div ng-if="bound">
  <div>{{::menu.menuItem1}}</div>
  <div>{{::menu.menuItem2}}</div>
</div>
<button ng-click="changeMenu()">Switch</div>

Then, in the controller:

$scope.bound = true;
$scope.menu = {..}; // menu 1
$scope.changeMenu = function(){
  $scope.menu = {..}; // menu 2
  $scope.bound = false;
  $timeout(function(){$scope.bound = true;}, 0);
}

Plunker

New Dev
  • 48,427
  • 12
  • 87
  • 129