Is expression inside ng-show fired after every model change in AngularJS? - angularjs

I have this simple controller markup
<div ng-controller="TestCtrl" ng-show="isVisible()">
<input type="text" ng-model="smth"/><br>
<span>{{smth}}</span>
</div>
And controller itself
function TestCtrl($scope, $log)
{
$scope.smth = 'smth';
$scope.isVisible = function(){
$log.log('isVisible is running');
return true;
}
}
Why after every little change of model (e.g. changing one letter inside textbox) I can see isVisible is running in console? It's not problem for such case but I think it will be in large application. Can I avoid this?

Liviu T. is right. You can't avoid having your isVisible function executed on every scope change. If Angular didn't rerun this function, than it could get out of sync with the rest of the code (for example if it was using $scope.smth model to resolve the return value).
Having this in mind, you should always try to make your functions idempotent/pure (always returns the same output given the same input) and light, because directive such as ng-hide, ng-show, ng-class and similar will always re-evaluate the expression assigned to them, on every $digest cycle.
Now, if you really don't want it to execute all over again, you might try some of the memoization techniques for caching the function output.

This is normal as this is essential pat of how AngularJS does its "magic". This answer has more details: https://stackoverflow.com/a/9693933/1418796
There are different techniques to make sure that you don't run into performance problems but no, in general you can't exclude those expressions from being evaluated.

Related

Angular.JS ng-Repeat in directive runs twice

https://plnkr.co/edit/WzLez5XElbOHRTvXEJFc?p=preview
function DirectiveController($scope, $element)
{
$scope.Array = ["a", "b"];
$scope.me = function() {
console.log("i ran");
};
};
//Directive HTML
<div class="cool picker" ng-repeat="item in Array">
<input ng-value="me()">
{{item}}
</input>
</div>
A function in directive controller is being called 4 times while directive runs only 2 times by ng-repeat.
I created a thine version of my code in Plunker, so suggestions like "get rid of isolated scope won't help".
i couldn't find a clean explanation here which focus only that matter with no massive code involved or Plunker example.
please advise...please make sure ur console is set on plunker]1
What you are seeing is the digest cycle at work. The digest cycle is how Angular’s auto-update magic works – it’s the reason that typing into an input box automatically updates anything that refers to its value.
When the digest cycle runs, it effectively redraws everything that might have changed on the page.
Angular uses some tricks to find everything that might have changed, and the main technique is watchers. These watchers are created automatically when you use directives like ng-if and ng-class, and when you use bindings like {{ yourBindingHere }}.
Each one of those things registers a watcher. When Angular’s digest cycle runs, every watcher is asked to update its state. In the case of ng-class, it will re-run the function bound to it, to see if anything needs to change. This is why your controller function runs multiple times, and it’ll run again each time something changes on the page.
Now since you are using interpolation {{}} it forces Angular to run once, and using a function with ng-value forces it another time.
The best practice is to not use function inside ng-repeat since it always forces the cycle to check for an update.
And at last use one time binding where possible to prevent any watcher to be created.

How to stop digest cycle manually in angularjs

As digest cycle do the dirty checking of the variable that is if there are 100 scope variables and if I change one variable then it will run watch of all the variables.
Suppose I have 100 scope model variables that are independent of each other. If I make changes in one variable then I don't want to check all other 99 variables. Is there any way to do this ? If yes, how ?
Surprisingly, this is usually not a problem, Browsers don’t have problems even with thousands of bindings, unless the expressions are complex. The common answer for how many watchers are ok to have is 2000.
Solutions :
It is fairly easy onwards from AngularJS 1.3, since one-time bindings are in core now.
One time Binding of the variables.
We can use One time binding(::) directive to prevent the watcher to watch the unwanted variables. Here, variable will be watch only once & after that it will not update that variable.
Stop the digest cycle manually.
HTML :
<ul ng-controller="myCtrl">
<li ng-repeat="item in Lists">{{lots of bindings}}</li>
</ul>
Controller Code :
app.controller('myCtrl', function ($scope, $element) {
$element.on('scroll', function () {
$scope.Lists = getVisibleElements();
$scope.$digest();
});
});
During the $digest, you are only interested in changes to Lists object, not changes to individual items. Yet, Angular will still interrogate every single watcher for changes.
directive for stop and pause the digest:
app.directive('stopDigest', function () {
return {
link: function (scope) {
var watchers;
scope.$on('stop', function () {
watchers = scope.$$watchers;
scope.$$watchers = [];
});
scope.$on('resume', function () {
if (watchers)
scope.$$watchers = watchers;
});
}
};
});
Now, Controller code should be changed :
<ul ng-controller="listCtrl">
<li stop-digest ng-repeat="item in visibleList">{{lots of bindings}}</li>
</ul>
app.controller('myCtrl', function ($scope, $element) {
$element.on('scroll', function () {
$scope.visibleList = getVisibleElements();
$scope.$broadcast('stop');
$scope.$digest();
$scope.$broadcast('resume');
});
});
Reference Doc : https://coderwall.com/p/d_aisq/speeding-up-angularjs-s-digest-loop
Thanks.
This is a good question and highlights one of the biggest deficiencies with Angular 1.x. There is little control over how the digest cycle is managed. It is meant to be a black box and for larger applications, this can cause significant performance issues. There is no angular way of doing what you suggest, but There is something that would help you achieve the same goals (ie- better performance of the digest cycle when only one thing changes).
I recommend using the bind-notifier plugin. I have no relationship with the project, but I am using it for my own project and have had great success with it.
The idea behind is that you can specify certain bindings to only be $digested when a specific event has been raised.
There are multiple ways of using the plugin, but here is the one that I find must effective:
In a template file, specify a binding using the special bind-notifier syntax:
<div>{{:user-data-change:user.name}}</div>
<div>{{:job-data-change:job.name}}</div>
These two bindings will not be dirty-checked on most digest cycles unless they are notified.
In your controller, when user data changes, notify the bindings like this:
this.refreshUserData().then(() => {
$scope.$broadcast('$$rebind::user-data-change');
});
(and similar for job-data-changed)
With this, the bindings for user.name will only be checked on the broadcast.
A few things to keep in mind:
This essentially subverts one of the key benefits of angular (also it's core weakness for large applications). Two way binding usually means that you don't need to actively manage changes to your model, but with this, you do. So, I would only recommend using this for the parts of your application that have lots of bindings and cause slowdowns.
$emit and $broadcast themselves can affect performance, so try to only call them on small parts of the $scope tree (scopes with few or no children).
Take a good look at the documentation since there are several ways to use the plugin. Choose the usage pattern that works best for your application.
This is quite a specific use-case to do exclusive/conditional checks in the digest cycle and I don't think it is possible without forking/hacking the angular core.
I would consider refactoring how/what you are $watching. Perhaps using ngModelController's $viewChangeListeners would be more suitable than $watch?

Angular Directive are Loading Multiple time

Is this a bug in Angular or am I missing something:
Calling a function from the directive's template shows that that function is executed 11 times ! with a templateurl, and 22 times !! with a string template.
angular.module('testDirective', [])
.directive('myDirective', function() {
return {
scope:{},
template: '{{increment()}} {{count}}',
controller: function($scope) {
$scope.count = 0;
$scope.increment = function() {
$scope.count += 1;
};
}
};
})
HTML:
<body ng-app="testDirective">
<my-directive></my-directive>
</body>
RESULT:
22
Here is a Plunker with both template and templateUrl methods.
This is quite an issue when method calls are involved within repeaters as an example, this ends up calling the same method dramatically more time than it should.
Anybody could shed some light on this ?
{{increment()}} in your view is going to be called every time the digest cycle is called, like any bound expression would be. If you were incrementing in the controller function itself, rather than a bound function called from the view template, that would indicate that the directive was "loading" multiple times. But the nature of Angular is that any bound expression is going to be checked for changes repeatedly during the digest cycle, and anything you expect to happen only once (or even a predictable number of times) should never happen inside an expression bound to a content element. On the other hand, expressions bound to event handlers only execute when the event fires.
The model takes some time to get used to. Though the old documentation argued against it, you can use functions in content binding expressions, but they should never change state. You have no real control over when $digest() is called, and if you're thinking in the Angular way, you won't want to.
I also found this post which relates to the same issue to be useful :
Using ng-class with a function call - called multiple times
Also one-time binding can be a solution is some case as related here :
Angular lazy one-time binding for expressions
increment() is being called every time the view is being updated. On load, increment() is called, and the count goes up by 1 which causes the view to be updated and increment() to be called again.
If you look in your console, you will see that Angular is telling you that it's stuck in an infinite $digest loop.

How to wait till angularjs is done with everything?

Similar to this question, I want to set focus on the last <select> whenever it gets added. As there's a single method doing it, I need no directive and no watch and no events. My function
$scope.addNew = function() {
$scope.items.push({});
$timeout(function() {
$("select").focus();
});
};
works nicely, except when called directly from the controller function definition like
angular.module('myModule').controller('MyCtrl', function($scope, $timeout) {
$scope.items = {};
...
$scope.addNew();
}
It looks like the timeout happens before the DOM gets constructed and $("select") is empty. With a delay of some 100 ms it works again, but this is a bad hack.
Contrary to what's said in the answer to the linked question, timeout doesn't suffice.
So what's a reliable way to wait for angularjs being really done with the DOM and everything?
Update:
It probably doesn't work because of the select to be focused being embedded in directives (including ng-repeat and some own ones) That's why there initially was no DOM element to focus on.
According to the comments, I need a directive. What's unclear is how exactly to do it. I tried and failed and found out a simpler solution.
What I need
I wasn't very explicit with this, so let me clarify.
I'm working with a table where each row contains some editable fields.
In addNew, I want to set focus on the first editable field of the new row.
In my case this happens to be the very last select.
It worked except at the very beginning, when I was adding the very first row from the controller body.
Why I'm opposed to using a directive
To my limited understanding, it's completely backwards:
A directive modifies the look, behavior, or structure of a given element. But there's no element which should be modified. I tried to put a directive on everything from the select itself to the whole body.
It needs to watch something or listen to an event, but I only want to invoke a function manually.
It didn't work (for me and others as the comments to the linked question shows).
I am going to try and influence you to use a directive here, just to perform the behavior.
Here is a fiddle.
Basic premise is adding the behavioral directive to the element inside repeater:
<table>
<tr ng-repeat="item in items">
<td>{{item}}: <input type="text" auto-focus/></td>
</tr>
</table>
Then your directive would put focus on the last added element:
app.directive('autoFocus', function(){
return function link(scope, elem){
elem[0].focus();
}
});
No watchers or events needed unless I am missing something that you require.
Code that manipulates the DOM should go in a directive, but if you switch to a directive and still have reason to wait until Angular is finished updating the scope and the dom, use $scope.$evalAsync:
$scope.$evalAsync( function() {
// This will wait until Angular is done updating the scope
// Do some stuff here
//
});
The solution was very trivial: Instead of calling $scope.addNew(); directly, I put it in $scope.init invoked from <form ng-init="init()">.
According to the documentation
The only appropriate use of ngInit is for aliasing special properties of ngRepeat, as seen in the demo below. Besides this case, you should use controllers rather than ngInit to initialize values on a scope.
this seems to be wrong (or maybe not, as ngRepeat si involved). I'm only using it to postpone the call to $scope.addNew();, where neither timeout nor posting events worked.

Does the $scope and $$phase workaround always work as expected in AngularJS?

One major thing to keep in mind when dealing with 3rd party tools as well as external'ish DOM events in AngularJS is to use the $scope.$apply() method operation to kickstart the changes. This works amazing, but sometimes the scope itself my already be grinding through a digest (which is basically what the $apply method triggers) and calling $apply when this is going on will throw an error. So to get around this, you will have to pay attention to the $scope.$$phase flag which is set to the scope whenever a digest is going on.
So now, lets say you want to change the URL and you fire off:
$scope.$apply(function() {
$location.path('/home');
});
And this works as expected, but now lets assume that the $scope is busy doing it's thing. So instead you check for the $$phase variable and assume that your changes will be picked up:
if($scope.$$phase) {
$location.path('/home');
}
else {
$scope.$apply(function() {
$location.path('/home');
});
}
This is what I've been doing (not with the code duplication obviously) and it seems to work 100% of the time. What I am concerned about is that how does AngularJS pickup the change when the scope is midway in it's digestion?
Maybe this example is not specific enough. Lets assume something bigger instead. Imagine if you have a huge webpage which plenty of bindings and lets assume that the digestion will chew through the page linearly (I'm assuming that it does something like this with respect to priority ... In this case being whatever shows up in the DOM tree first) and update the bindings on the page from top to bottom.
<div class="binding">{{ binding1 }}</div>
<div class="binding">{{ binding2 }}</div>
<div class="binding">{{ binding3 }}</div>
<div class="binding">{{ binding4 }}</div>
<div class="binding">{{ binding5 }}</div>
<div class="binding">{{ binding6 }}</div>
<div class="binding">{{ binding7 }}</div>
<div class="binding">{{ binding8 }}</div>
Lets assume that a digestion is going on and it's somewhere near the middle of the digestion queue. Now lets try and change a binding value at the top of the page somewhere.
if($scope.$$phase) {
$scope.binding1 = 'henry';
}
Now, somehow, AngularJS picks up the change and updates the binding properly. Even though the change itself can be considered to take place earlier in the queue with respect to the HTML/DOM.
My question is that how does AngularJS manage this potential race condition? I can somewhat be comfortable if binding8 updates (since it's lower down the page), but because binding1 also updates (right away without the need to call $apply again), this makes me a bit lost. Does this mean that another digestion was dispatched somewhere in between? Or is the $scope object more magical than I expect to be? I would assume that this issue has been though of before, but since finding out about $$phase and $scope in the first place was a bit tricky then I'm assuming that this also might be something that fell through the cracks.
Any ideas?
Regarding the bindings and race conditions. $digest will loop all the watchers until there is no changes. As you can observe by adding logs to watchers/binded methods it will call each binding/watcher at least twice to be sure that there is no change and all binded values are stable. Those are just dirty checks that run until every value is resolved (that have not changed within 2 loop iterations). Hope that helps.
This is explained in AngularJS docs here: http://docs.angularjs.org/api/ng.$rootScope.Scope#$digest
NOTE: This is a copy/paste of my comment as requested by matsko.
Apply will keep calling digest untill it is sure nothing has changed. So if may have a race condition at the first call, but a second one will always compensate.
You shouldn't use that workaround anyway: if the state of your $scope in uncertain on this function, you should digest it higher on your call stack where you know if you're synchroneously processed by angular, thus no need to digest, or if you're modifying the model asynchroneously so you can digest it.
On a side note, $scope.$applyis basically a $scope.$root.$digest with error handling, so if you're updating an isolated scope within a directive you can save performances by calling $digest instead of $apply:
// Somewhere in a callback
$location.path('/home');
$scope.$digest();
PS: That's for correcting e-satis answer: $digestwill also call itself while the scope is dirty.

Resources