0

I'm making a bookmark manager for Chrome in angular using the chrome.bookmarks API.

For my proof of concept, my bookmark page is a single list of all my bookmarks. I'm using 2 directives two iterate through the entire collection of my bookmarks, a collection directive and a member directive. I'm having performance issues, and I'm looking for direction.

tl;dr: model changes to take forever to update on my view, what am I doing wrong, and how can I do it better?

Here is the relevant code:

app.controller('bookmarksController', ['$scope', 'ChromeBookmarks', function($scope, chromeBookmarks) {
  chromeBookmarks.getBookmarks().then(function(bookmarkTree) {
    $scope.bookmarks = bookmarkTree;
  });
});

index.html

<div ng-controller="bookmarkController"> 
  <collection collection="bookmarks"></collection>
</div>

collection.js

app.directive('collection', function($compile) {
  return {
    restrict: 'E',
    scope: {
      collection: '=',
    },
    templateUrl: 'collection.html',
  };
});

collection.html

<ul>
  <member ng-repeat="member in collection" member="member"></member>
</ul>

member.js

app.directive('member', ['$compile', function($compile) {
  return {
    restrict: 'E',
    scope: {
      member: '=',
    },
    templateUrl: 'member.html',
    link: function(scope, element, attrs) {
      if (scope.member.children) {
        var collectionTemplate = '<collection collection="member.children"></collection>';
        var collection = angular.element(collectionTemplate);
        element.append(collection); 
        $compile(collection)(scope);
      }      
    }
  };
}]);

member.html

<li>
  <bookmark ng-if="member.url" node="member"></bookmark>
  <folder ng-if="!member.url" node="member"></folder>
</li>

bookmark.js

app.directive('bookmark', function() {
  return {
    restrict: 'E',
    scope: {
      node: '='          
    },
    templateUrl: 'bookmark.html'
  };
});

bookmark.html

<div>
  {{ node.title }} <button ng-click="open()">Live Site</button>
</div>

folder.js

app.directive('folder', function() {
  return {
    restrict: 'E',
    scope: {
      node: '='
    },
    templateUrl: 'folder.html'
  };
});

folder.html

<div> {{ node.title }} </div>

As you can see in my controller above, I only have one model attached to the scope: $scope.bookmarks. I only want to load this variable once.

Once the page has loaded, I want to attach listeners to the chrome.bookmark events, so that if a bookmark changes, the event triggers, and I only need to change the affected bookmark.

My current method for doing this is using a function that takes the bookmark that changed and recursively moves through $scope.bookmarks, and replaces the bookmark's parent with an updated version that I get from the chrome.bookmarks API. The function basically does something like: $scope.bookmarks[1].children[2] = updatedBookmark

I've tried adding the listeners to my page, but creating a bookmark and the consequent updating of the view takes 3 seconds (bad), and if I bookmark every tab in a window, the page freezes up (worse). I do have approximately 2k bookmarks, but I still want this to be performant beyond that level.

My understanding is that poor angular performance is related to having a ton of watchers, because the $digest cycle becomes too massive. Is this what you think is causing my performance problems?

If all I want is for my modifying the $scope.bookmarks model to trigger the view to update, how many watchers do I really need?

I know that 2 way data binding is meant to keep models and views in sync, but I can't help but think that in my case, where I only want to change a little bit of data (and the changes are based on the bookmark API events, not necessarily user input), that 2 way data binding seems wrong, especially because the $digest loop seemingly checks so much data.

But then again, I do need the view to properly mirror the model data. If I get rid of the 2 way data binding, and use ngOnce for one time binding, could the changes to my model ever be rendered?

Am I just running into the natural limitations of angular and need to use a different framework?

Xan
  • 74,770
  • 16
  • 179
  • 206
markain
  • 371
  • 5
  • 15

1 Answers1

1

Angular cannot handle more than ~2000 watches,some browser can do more but that is a good cap. There are 2 possibilities for the slow updates:

1) Are the APIs you are calling withing the Angular life cycle? If so make sure you do a scope.$apply(). 2) You have too many watches. There are code snippets you can find that you run in the console that will spit back the number of watches If they are high then consider buffering your view so that only what you see is bound, github.com/EnzeyNet/VirtualScroll.

Enzey
  • 5,254
  • 1
  • 18
  • 20
  • I am already telling the view to update using scope.$apply, that's what's triggering the digest loop and slowing everything down – markain Nov 25 '14 at 17:54
  • How many watches do you have registered? Try a script like the one here: http://stackoverflow.com/questions/18499909/how-to-count-total-number-of-watches-on-a-page – Enzey Nov 25 '14 at 20:16