Understanding bindonce limitations - angularjs

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.

Related

ng-repeater executing every digest cycle

I am trying to understand how angular 1 digest cycles work and how they impact the existing scope. I have 2 controllers one of them is using angular material with a repeater. The second controller is a simple button click event. Both events print to the console to see what is happening.
What i am seeing is that every time i click the button on the second controller the repeater re-runs its entire calculation?
Is this how angular is intended to work? Please see attached the following codepen - when the button is clicked the repeater re-runs on the first controller every single time. I assume this is going to happen every single time any operations occurs on any controller - which just seems like madness.
the repeater code is as follows:
<div flex="50" ng-repeat="item in items">
<md-checkbox ng-checked="exists(item, selected)" ng-click="toggle(item, selected)">
{{ item }} <span ng-if="exists(item, selected)">selected</span>
</md-checkbox>
</div>
At first i thought there was something wrong in my angular but it seems like this happens anywhere full codepen bellow.
Codepen Example
If you don't want ng-repeat to rerun on change then use "track by $index" in ng-repeat
yes this is exactly how it is supposed to work. That is the nature of two-way binding, you constantly check whether one of both values changed.
If you want to turn off that feature and use a one-time binding you can use the :: syntax.
see in the documentation: https://docs.angularjs.org/guide/expression (you need to scroll down to One-time binding. Sadly there are no anchors :D)

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.

Improving AngularJS performance with constant expressions and manual view refresh

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.

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!

Resources