12

Today I had to fix a performance issue caused by this code: Pay attention to updateStats called inside the template

<script type="text/ng-template" id="entityGrouper">

<section>

<div>
<ul ng-click="hideEntityBox = !hideEntityBox">
<li>
{{ entityNode.name }}
</li>
<li ng-repeat="breadcrumbItem in entityNode.breadcrumb">
{{ breadcrumbItem }}
</li>
</ul>
{{ updateStats(entityNode) }}
<span ng-include="'/mypath/views/resume.html'"></span>
</div>

<div>

<div ng-repeat="entityNode in entityNode.group" ng-include="'entityGrouper'"></div>
<div ng-repeat="entity in entityNode.group" ng-include="'entityBox'"></div>

</section>

</script>

Template use:

<div ng-repeat="entityNode in entityNode.group" ng-include="'entityGrouper'"></div>

After debugging this code I discovered that this function was called many more time than the array size (my array has 4 objects and the function was called more than 100 times), even mouse hover called this function. I fixed that by just putting a ng-init inside the template and now it's working properly, but I didn't figure out why this function has been called so many times. Is there something about two way data binding?

thiagoh
  • 6,285
  • 8
  • 44
  • 74
Victor Laerte
  • 6,020
  • 12
  • 50
  • 93

3 Answers3

7

It is usually recommended to use a $watch for scenarios like this. Since you are binding a function {{updateStats()}}, it will execute on every digest cycle.

So in your code whenever a digest cycle is called, it will also call the function. And digest cycles are frequently called internally in Angular.

Jagrut
  • 884
  • 6
  • 20
4

What you are seeing is the result of a digest cycle.

Adding a :: will call the function once / one-time binding.

{{ ::updateStats(entityNode) }}

Ali Gajani
  • 13,720
  • 8
  • 49
  • 83
  • what :: exactly do? – Victor Laerte Nov 24 '16 at 17:48
  • 2
    `::` offers one time binding. If you prefix an expression with this, it will stop running or recalculating after the first digest. More information *One-time binding* section of the [documentation](https://docs.angularjs.org/guide/expression). – Ali Gajani Nov 24 '16 at 17:51
4

This is happening not because of two-way data binding (it is applicable to form inputs), but rather because of angular change detection. Angular periodically checks if any of the values, bound to a template, changed, and if so - updates its value in the view. Typically, it is triggered by user generated events. In order to check if the value of updateStats(entityNode) changed, Angular evaluates it, which causes the performance hit.

To fix this, you may use previously mentioned one-time binding updateStats(entityNode) if the result is set once and will never change in the future. I guess, this is your case, and you have already moved the evaluation to ng-init. For values that update with time, it would be better to create a separate property in entityNode like entityNode.stats, display it in the template and run the updateStats(entityNode) in your controller or service only when necessary. By doing that, you will prevent running updateStats(entityNode) on each digest cycle (you have already seen how often that is) and thereby improve your app's performance.

EternalLight
  • 1,249
  • 1
  • 10
  • 18