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?