Improving AngularJS performance with constant expressions and manual view refresh - angularjs

Lately I was thinking about performance of AngularJS application when you need to present rich domain model (lots of ngRepeat and expressions {{}} - e.g. rendering spread-sheet-like model).
I came up with pretty simple solution using constat watch expressions and ngRepeat over a stamp (revision) property - check plunker:
<!-- Outer ngRepeat is on a watched dummy object - when changed, ngRepeat will re-generate its contents -->
<ul ng-repeat="stamp in [data.stamp]">
<!-- Inner constant ngRepeat, which can be pretty expensive in case of long iterations -->
<li ng-repeat="item in ::data.items">
Item {{::item.value}}.
</li>
</ul>
My question is: Is there any directive specifically for this approach (i.e. refreshing contents when a specified watched value changes)? Using ngRepeat does the job, but feels a bit hacky (no need for a child scope).
And also: Do you see any issue with the proposed approach?
One remaining bottleneck I see is that with ngRepeat a lot of scopes are still being created and $rootScope.broadcast can get a bit expensive. But overall this can greatly improve page performance.

Related

Is there a way to stop angularjs from watching a variable for changes?

I have a variable I need to put in a template, but it's only created and never updated or removed. It is, however, referenced in multiple areas of the template. It's used in ng-repeat, so it's an object. Not sure if that matters.
But I want to reference the variable once and stop angularjs from watching it. Is this possible?
Are you familiar with bind once?
Angular internally creates a $watch for each ng-* directive in order to keep the data up to date, so in this example just for displaying few info it creates 6 + 1 (ngRepeatWatch) watchers per person, even if the person is supposed to remain the same once shown. Iterate this amount for each person and you can have an idea about how easy is to reach 2000 watchers. Now if you need it because those data could change while you show the page or are bound to some models, it's ok. But most of the time they are static data that don't change once rendered. This is where bindonce can really help you.
https://github.com/Pasvaz/bindonce
<ul>
<li bindonce ng-repeat="person in Persons">
<a bo-href="'#/people/' + person.id"><img bo-src="person.imageUrl"></a>
<a bo-href="'#/people/' + person.id" bo-text="person.name"></a>
<p bo-class="{'cycled':person.generated}" bo-html="person.description"></p>
</li>
</ul>

ng-repeat with track by over multiple properties

I have a problem with angular ng-repeat directive.
Currently I work on some project where from the API I get a list of items (some times it could be 1k items) and this list should be refreshed every 5 seconds (it is monitoring related project).
When the list length is a little bigger the website while re-rendering DOM could "slow". It comes out that angular regenerate the whole DOM (but 95% of item are the same ! )
One of the possible approach is to set "track by" expression for example to item.id. But here comes another problem, I also want regenerate items when for example descriptions was changed by other user. Since track by is expression to item.id changes in item.description didn't update item in DOM.
There is way to track by over multiple properties? Maybe some function?
Or maybe do comparison by "hand" ?
Any ideas, code samples I would appreciate :)
UPDATE
what I discover when I set track by to item.id angular didn't re-crete html for items, just update value in already created element and it seems to be "faster" then removing and creating.
Previously I though a little bit different.
FIX
For those who are looking for better performance over >1k items in ng-repeat USE track by item.id it will boost your performance ;)
You do not need to create a function to handle track by multi properties.
You can do:
<div ng-repeat="item in lines track by item.id+item.description">
As the comment suggested you could try something like this:
<select ng-model="item" ng-options="item.id as item.description for item in items track by itemTracker(item)">
In your controller:
$scope.itemTracker= function(item) {
return item.id + '-' + item.description;
}
This might help with the number of DOM elements being re-rendered when the list changes.
Based my knowledge, the angularjs model is bind to the ui view, so the model will rerender via $apply or $digest once the value changed. so in your case, u wan bind the model value to ui view but also do not want to re-render the view if the value has not change,that is impossbile. this is what i know.
however, u can just manipulate the dom element. for example
store the data to a variable
var x = [{id:"id1",value:"v1"},{id:"id2",value:"v2"}]
in html, manual append or using directive to append, then assign the id to the element,
<div id="id1">v1</div>
check and compare the value, based ur needs.
once found, then angular.element("#yourid").text()
this will solve your browser resources consume problems.

Custom Angularjs directive vs. ng-class

Several times when creating or customizing a directive (either my own directive or for example https://github.com/dpiccone/ng-pageslide) I get a point where all the display logic is controlled by a single css class. At that point the directive boils down to adding and removing a single class. So instead of using a new directive I can simply use the ng-class directive (see an example here: https://gist.github.com/Hypercubed/8f40556eb0f6eddbcca3). Is there an advantage to the custom directive approach vs the ng-class/CSS styles approach? I guess the custom directive doesn't depend on $animate? Am I just doing it wrong?
Sorry for another directive vs. XXX question.
I think you're failing to see the forest for the all tress. You're focusing on a very minute detail and missing the larger picture. Directives are more than simply applying styles. I think an example is best. For example, take the rating directive. If you wanted to render a star rating model it might look like this:
<div ng-rating="album.rating" max="5"></div>
That may add the following to the DOM:
<ul class="inline">
<li ng-repeat="i in max">
<i ng-class="{ 'icon-start-empty': i > rating, 'icon-star': i <= rating }"></i>
</li>
</ul>
Under the covers ng-class it utilized, but that is only a part of the logic encapsulated in the rating directive. It allows the user to configure how many stars the rating is out of, and renders the same number of li elements. Then because you wrote a directive it allows you to reuse this logic where ever it's required. Using ng-class only works in that 1 location. If you want to do the same thing you're copying code which is a sign maybe you want to wrap that logic up in a directive.

Angular adds extra dom elements in ng-repeat when using filter

I recently updated angular from 1.1.5 to 1.2.14.
Now the filters in the ng-repeats produce some unexpected behavior:
ng-repeat renders as normal
enter text into filter model input : ng-repeat does not filter
remove text from input and angular adds extra dom elements to the ng-repeat, basically repeating the ng-repeat. The array bound to the ng-repeat does not change though.
repeating steps 2 and 3 causes more elements to be added
I have tried to recreate this in PLUNKR and it works fine. Any idea what could be causing this?
code:
<input type="text" data-ng-model="query" >
<div class="default-add" data-ng-repeat="array in arrays.arrays | orderBy:'name' | filter:{name: query}">
<div class="default-add-image">
<h1>{{array.title}}</h1>
</div>
</div>
The data is valid json, I have tested with mock data and have the same error.
Thanks
Removing the NG-REPEAT from the repeated element and adding it to a parent container solved it.
If it is running in Plunker fine, then most likely the problem lies in your code. Is this a custom filter? If yes, I would start looking there. If not, then I would check if it works properly on artificial data (thinking that the data might be the issue). If this does not work, then look at your data provider. If this works, then look into how you are processing the data.
I doubt that this is Angular's bug. I migrated from 1.0.7 to 1.2.x recently without such problem (there were few, but all fixed easily by following migration guidelines from Angular team).
Hope that helps!

Understanding bindonce limitations

I've been reading about bindonce as a way of reducing watches and enhance performance. In an effort to better understand the package i made an example with ng-repeat.
JSBIN here
Without bindonce I'm getting 103 watches, 100 list items + 2 buttons.
Using bindonce i'm getting 3 watches, 2 bottons + 1 fort the list.
If I understood binonce correctly, it removes the watch once the binded object is resolved and rendered. So,
How is it possible that using bindonce, changes made to the object are still reflected in the DOM ?
There's a hint in the documentation:
Now this example uses 0 watches per person and renders exactly the same result as the above that uses ng-. *(Angular still uses 1 watcher for ngRepeatWatch)
The key is that Angular still keeps a watch on ngRepeat, so if the array changes ngRepeat will re-render the array and the bindonce functionality is re-applied.
I've updated your jsbin example here to better illustrate this http://jsbin.com/xugemico/2/edit
Notice the following addition:
<p>
Bindonce: first item:
<span bindonce="arr" bo-bind="arr[0]"></span>
</p>
The above code utilizes bindonce on the first array item without ngRepeat's watch in play, you'll see that the value is not updated as per the bindonce inside ngRepeat.

Resources