AngularJS is great. I started with BackboneJS as frontend MVC, but soon switch to AngularJS because of the concept AngularJS adds to HTML. AngularJS allows me to get simple things get done very quickly, watch a value for changes and update the views whenever necessary.
Further for more complex things AngularJS offers just the right structure for code organization. After some effort to learn to use directives and services, AngularJS forces me to get my code clean. This means after some months of using the framework intensively, if things get complicated I know I am doing something wrong. So I need to rethink what I am doing and often a much simpler solution is found.
For one of our projects, vizit.lu (Sorry, the site is no longer available), a map based real-estate search platform, I used AngularJS for the frontend. Once the initial HTML and scripts are loaded only images and JSON is transferred. When ever the user drags the map, the list of estates is updated. So you only see estates that are in the visible part of the map. We designed the whole stack to be fast and the map feels responsive. Our initial goal was to get the estates on the map and in the list under 400ms.
After some testing, the response times were very satisfying, however the application was blocking for 500-700ms after each refresh of the view. Quickly I stumble upon the problem: ng-repeat. The server reponse arrived at the client within 100ms and then the browser took almost a second to render the list and markers on the map, this was not a satisfying user experience.
I was expecting this bad performance due to the internal working of AngularJS. In short, ng-repeat creates bad performance and AngularJS is getting slower with more than 2000-2500 two-way bindings to watch. But for ng-repeat you often want the items to be rendered and then forget about them as they are not updated from the client side. So no two way binding required. The was also true for us, render the html and forget about the bindings.
I tried several different solutions, so here is what I found:
- https://github.com/Pasvaz/bindonce Bindonce allows to create a template to render that uses jQuery to render the HTML, so no two-way binding. This was the first solution i tried and even though I expected this to solve the problem, i was surprised to see it did not. Performance improve only slightly, maybe we gained 100ms to render, but the rendering still took over 500ms.
- http://blog.stackfull.com/2013/02/angularjs-virtual-scrolling-part-1/ Virtual Scrolling just renders the visible elements and removes invisible elements from the DOM. In fact this solved the problem of the slow rendering, but created another one namely that scrolling now had bad performance. On mobile scrolling wa not controllable. Swiping down or up produced unexpected results.
- Final solution: Upgrade to AngularJS 1.1.5 and use limitTo together with Infinite scrolling. AngularJS ng-repeat offers from version 1.1.4 the limitTo option. I slightly adapted the Infinite Scroll directive to make scrolling within a container possible that does not have height 100% of window.
ng-repeat="item in items | orderBy:prop | filter:query | limitTo:limit"
Notice that limit is a variable in the $scope, so changing it automatically adjusts the number of rendered items. And incrementing limit, only renders the added elements.(The drawn elements in the DOM remain untouched) From StackOverflow question.
The limitTo with Infinite Scrolling plays very neat together with the Track By feature for ng-repeat. So updating only those elements that change and an object id can be passed to identify equal objects. Very nice for ng-animate as only those elements are animated that actually change, play around with it on vizit.lu.
NOTE: when using TRACK BY be sure to add it correctly to the ng-repeat line.
My element now looks like this:
<div ng-animate="'animate'" ng-repeat="estate in estates | orderBy: orderProp : orderReverse | limitTo: config.itemsDisplayedInList track by estate.id">
UPDATE: here is my directive if it helps