Angular performance with function expression - javascript

What are the performance implications of running a function within an Angular expression? For example:
<button ng-if="isValid()">Valid</button>
I'm assuming the function isValid() is being run for each digest loop, which occur many times per second. What options are available within Angular for improving the performance here?
One idea would be to run isValid() within a timeout function a few times a second, then setting a scope variable, so I can control the speed of the "digest". Are there other options people use?

I think if function body is just a simple logic comparison, won't affect performance, but if it gets complex, you can try profiling your code via chrome tools to see how much is performing,
Another alternative is also you can try binding the ng-if to a controller scope property instead and modify the value from other part of your controller's code, so maybe you can apply kind of debounce (https://github.com/shahata/angular-debounce) technique to avoid set the scope variable tons of times

It looks like you're checking if a form is valid before submitting. First of all, your ng-if on the button defeats the purpose of using the same function to conditionally apply a class, since it won't even be in the DOM if isValid() returns false.
You should be able to use ng-pattern, ng-change hooks on your inputs to determine validity as the user is entering form values. Then on your button you can apply the class based on the form's validity:
<button ng-class="{
'btn-success': myFormName.$valid,
'btn-danger': !myFormName.$valid
}" ng-bind="(myFormName.$valid) ? 'Valid' : 'Invalid'"></button>
If you want to suppress form submission, you can check validity of the form inside a function with ng-submit on the <form> tag:
<form name="myFormName" ng-submit="myFormName.$valid && mySubmitFunction()">

One way is to run the isValid function within an interval function, setting a simple scope variable within. For example:
$interval(function() {
scope.isvalid = scope.isValid();
}, 100);
This will run every 100ms, but is probably preferable to the number of calls the digest makes. You can also use $watch or $observe to reduce the number of calls.

Related

What is the performance of checking an attribute vs checking a return value of a function?

I have a list of items in a component:
list: Array<MyType>;
The user can select and deselect elements on click:
toggleItem(item: MyType) {
if (this.selection.has(item)) {
this.selection.delete(item);
return;
}
this.selection.add(item);
}
The selected items are stored in a Set:
selected: Set<MyType> = new Set();
Now I need to toggle a CSS class and a title-attribute depending whether an element is selected or not:
<button class="toggle"
type="button"
[ngClass]="{'selected': selection.has(item)}"
[title]="selection.has(item) ? 'Select' : 'Deselect'"
(click)="toggleItem(item)">
{{ item.title }}
</button>
Now I've read somewhere that it is a bad idea, to evaluate function calls as Angular periodically will call them for change detection, like here:
[ngClass]="{'selected': selection.has(item)}"
They say that it's better to check a variable or a member of the object instead, like:
[ngClass]="{'selected': item.selected}"
Is is true and does it decrease performance the way I use it currently? Should I add a property to each item that is set when it's added to or removed from the Set?
Whenever Angular performs change detection, it checks whether anything that's variable and is in the template has changed.
Now, the check is pretty straight forward in case of model variables as Angular can simply read values of them in order to check for a change.
But that's not really the case with a function. With functions, Angular can only determine if the variable has changed by calling the function itself.
Now it doesn't make much of a difference if the function is a one-liner return of a value.
But in case the function has complex logic, it will essentially kill all the performance. As every time the change detection cycle runs, Angular will call that function in order to check for changes and detect them.
Hence, it's not really recommended to write functions in data-binding syntax in templates.
NOTE: I've also written a Medium Article about this. I'd suggest you check it out. It's mainly about Angular Reactive Forms performance. But it will help you better understand what I said above.

Angular ng-disabled one time binding with function

one time binding for ng-disabled expression can be added like below: It works fine. No watcher is alive once isDisabled is resolved.
<text-area ng-disabled="::isDisabled"></text-area>
How can we add one way binding for ng-disabled with function. Tried like below but watcher was still present on the element.
<text-area ng-disabled="::isReadOnly(name)"></text-area>
Use like it.
<textarea ng-disabled="::{{test()}}"></textarea>
JS :
$scope.isDisabled = true;
$scope.test = function() {
return $scope.isDisabled;
}
The problem in my case is, there is no method called isReadOnly defined and since it will result in undefined, the watcher will be alive always since it could not come to stable position as specified in the Value stabilization algorithm in angular docs.
When digest loop is done and all the values have settled, process the queue of watch deregistration tasks. For each watch to be deregistered, check if it still evaluates to a value that is not undefined. If that's the case, deregister the watch. Otherwise, keep dirty-checking the watch in the future digest loops by following the same algorithm starting from step 1

When exactly is ng-checked called

While using AngularMaterial, I have ng-checked like this:
<md-list>
<md-list-item ng-repeat="option in options">
<p> {{ option }} </p>
<md-checkbox class="md-secondary" aria-label="{{$index}}" ng-checked="exists($index)" ng-click="toggle($index)"></md-checkbox>
</md-list-item>
</md-list>
And my exists function:
$scope.exists = function (optionNum) {
console.log('Inside $scope.exists. option: '+optionNum);
};
My timer:
function updateTimer() {
var onTimeout = function(){
mytimeout = $timeout(onTimeout,1000);
}
var mytimeout = $timeout(onTimeout,1000);
}
With this, the $scope.exists function is getting called every second. Can someone please explain how ng-checked and $timeout related ? and how to avoid this ?
Reason in one word is: digest cycle. Since your function is bound to the view, every time digest cycle happens those expressions gets evaluated as a part of dirty check to make sure if respective DOM needs to be updated or not. This is nothing to do with angular material alone, it is the core angular implementation. Now in your case you are calling $timeout infinitely which means after each timeout execution digest cycle happens to perform dirty check.
Now what you have is fine, but whenever you bind a function to DOM (as a part of view binding, interpolation or property state attributes or even DOM filters - of course events are fine) you should be aware of the fact that you do not perform extensive operation in that function accidentally or intentionally as the app grows, it will slow down the entire app, and will be hard to refactor and diagnose when the app grows larger and problems starts happening. As much as possible bind to a property instead of a function. Note that even if you bind a property still angular $parse creates a getter on it and adds it to the $$watchers queue to be dirty checked every digest cycle, but difference is that it is a simple getter function.
So basically for instance in your case you could bind ng-checked to property
..ng-checked="doesExist"
and set the property doesExist whenever it needs to be updated. So with this instead of checking for the existence every time, you explicitly set the respective property when a respective event happens. That makes the logic explicit as well.
ng-checked, like many of angular's directives are based on watches. Anytime a digest cycle is called, it evaluates all of the watchers (which the function you are using is one of). So every time $timeout evaluates it is starting a new $digest cycle and evaluating all of the watchers. This is part of the "magic" that keeps the view updated with all of the data in your controllers and directives.
Watchers can become a performance issue if you make your functions complex, or make TONS of watchers. It is generally best to have simple logic that returns true or false very quickly and to avoid setting watches on everything.

$watch vs. ngChange

Say that you want to do something when a property of $scope changes. And say that this property is bound to an input field. What are the advantages/disadvantages of using $watch vs. using ngChange?
html
<input ng-model="foo" ng-change="increment()">
<p>foo: {{foo}}</p>
<!-- I want to do something when foo changes.
In this case keep track of the number of changes. -->
<p>fooChangeCount: {{fooChangeCount}}</p>
js
// Option 1: $watch
$scope.$watch('foo', function() {
$scope.fooChangeCount++;
});
// Option 2: ngChange
$scope.fooChangeCount = 0;
$scope.increment = function() {
$scope.fooChangeCount++;
};
http://plnkr.co/edit/4xJWpU6AN9HIp0OSZjgm?p=preview
I understand that there are times when you need to use $watch (if the value you're looking to watch isn't bound to an input field). And I understand that there are times when you need to use ngChange (when you want to do something in response to a change in an input, but not necessarily in response to a scope property change).
However, in this case both accomplish the same thing.
My thoughts:
ngChange seems cleaner, and easier to understand what's happening.
$watch seems like it might be slightly faster, but probably negligible. With ngChange, I think Angular would have to do some extra work in the compile phase to set up the event listeners, and perhaps extra event listeners decrease speed a bit. Regardless of whether or not you use ngChange, the digest cycle runs on changes, so you have an opportunity to listen for something and call a function in response to changes.
Bottom line - You can achieve with $watch every thing you can achieve with ng-change but not vice-versa.
Purposes:
ngChange - binded to a HTML element
$watch - observing scope's model objects (HTML object models included)
My rule of thumb - if you can use ng-change use it to match your scenario, otherwise use $watch
Why you shouldnt use $watch?
It’s inefficient - Adding complexity to your $digest
It’s hard to test effectively
It's not clean
You have it mostly right. ng-change is very DOM specific and for evaluating an expression when the change event fires on a DOM element.
$watch however, is a lower-level (and more general purpose) utility that watches your view model or $scope. So your watch function will fire every time the user types a key (in the example of an input).
So to contrast, one listens to DOM events, the other watches your data.
$watch adds more complexity do the $digest, making it less efficient. In your case ngChange it's a cleaner and easier solution...
Font: http://www.benlesh.com/2013/10/title.html

AngularJS: Using JS functions inside directives - bad practice?

I have this part of code inside ng-repeat:
<div ng-show="parameter == 'MyTESTtext'">{{parameter}}</div>
where parameter is some $scope string variable ...
I wanted to see if its possible to check (inside ng-show for instance) whether parameter contains a substring.
You could do this with:
<div ng-show="parameter.indexOf('TEST') != -1">{{parameter}}</div>
which seems to be working; it displays every parameter that contains 'TEST' keyword.
I was wondering:
is this a correct way of doing this within AngularJS app?
Is it OK to use javascript built in functions like that?
EDIT:
parameter is actually formed like this: (and is thus not a $scope variable as I said above, sorry)
<div ng-repeat="(parameter,value) in oneOfMyScopeArrays">
UPDATE
Since you're dealing with strings in ngRepeat and not objects, there's no place to set flag to in your data elements. In this case I would advise using a custom directive. I do not agree with Darryl Snow's opinion that directive in this case is redundant. With directive (as it was the case with flag in controller) you can evaluate parameter once instead of doing so in every $digest cycle. Furthermore, if you decide to implement same functionality in other template, instead of copying the expression around, which is redundant, you'd reuse same directive. Here's a quick idea of such directive:
.directive('parameter', function() {
return {
link: function($scope, $element, $attrs) {
$attrs.$observe('parameter', function(parameter) {
if (parameter.indexOf('TEST') == -1) {
$element.hide();
} else {
$element.text(parameter);
$element.show();
}
});
}
}
});
Template:
<div parameter="{{parameter}}"></div>
This directive even sets up one watcher less per parameter comparing to your original solution, which is better performance wise. On the other hand, it disables two-way binding (parameter text is rendered once), so it won't work in case you want to edit parameter string in place.
ORIGINAL ANSWER
Is it correct way? Technically yes, because it works. Is it OK? Not so much because of several reasons:
Performance. Everytime $digest loop runs (it might run quite a lot, depending on interactivity of application), it has to process every such expression. Therefore string parameter.indexOf('TEST') != -1 has to be parsed and evaluated, which means calling .indexOf up to several times after each interaction, for example click on element with ngClick directive. Wouldn't it be more performant to test this assumption parameter.indexOf('TEST') != -1 once in Controller and set a flag, e.g.
$scope.showParameter = parameter.indexOf('TEST') != -1
In template you would write
<div ng-show="showParameter">{{parameter}}</div>
Model logic in template. It's hard to tell the actual reasoning from your example when the parameter should be visible, but is it up to the template to have this logic? I think this belongs to controller, if not model to decide, that your view layer would be decoupled from making assumptions about how the model actually works.
Yes, perfectly ok I think. You could write a separate directive of your own that does the same thing - it may look a bit tidier but it's ultimately redundant when angular comes with ng-show already built in, and it means a slight additional payload to the user. You could also do a $scope.$watch on parameter and set another scope variable for ng-show, but that just moves the mess from your view to your controller.
$scope.$watch('parameter', function(){
if(parameter.indexOf('TEST') != -1)
$scope.showit = true;
});
and then in the view:
<div ng-show="showit">{{parameter}}</div>

Categories