Angular trying to understand method for checking if element isActive - angularjs

I'm following thinksters angular nfl fantasy tutorial here http://www.thinkster.io/angularjs/eHPCs7s87O/angularjs-tutorial-learn-to-rapidly-build-real-time-web-apps-with-firebase and they implement the ng-class active in this way...
<li data-ng-repeat="entry in navbarEntries" data-ng-show="auth" data-ng-class="{ active: entry.isActive }">
$scope.$on('$routeChangeSuccess', function() {
$scope.navbarEntries.forEach(
function(data) {
data.isActive = ($location.path().indexOf(data.link) == 0);
}
)
})
They have this explanation for the implementation below...
"Why, might you ask, wouldn't you just call a method from the view, which would assess if it should be active or not?
The reason is the $digest cycle re-evaluation. This cycle occurs very, very often, and every method call you use in your view to assess property will be re-invoked every single cycle, which will obviously degrade performance as the application scales."
I'm wondering what it would look like to "call a method from the view", what "to assess property" means, and would like a bit more of an explanation in what cases to use like this to avoid $digest cycle re-evaluation is appropriate in angular.

To call a method from the view would look something like this:
<li ng-class="{active: urlIsActive('/home/')}">Home</li>
Then urlIsActive would be a function in the scope, like this:
$scope.urlIsActive = function(url) {
return ($location.path().indexOf(url) == 0);
}
The problem with this approach is that the function urlIsActive would have to be re-evaluated on every scope digest cycle, wasting a lot of performance. The approach they suggest is using events to manipulate scope variables instead, which is cheaper in terms of performance.
Note that the suggested scope variable manipulation occurs when an event is fired. When rendering the page, finding out if a link should be active is just a simple variable lookup, rather than actually executing a function (which has a lot of overhead in itself).

Related

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?

Does function binding have any concerns or issues in performance in angularjs?

I wanted to know that when binding a function call in html, has any performance issue, rather if we use property binding.
I have tried,
<span class="alarm-count" ng-if="bindFun()">My bind</span>
whereas in my controller,
I have called it as,
$scope.bindFun = function(){return service.bindDataFun()};
Further, in service.
service.bindDataFun=function(){
return true // On condition evaluation.
}
I don't think it is a big deal. The ng-if expression will be called in every digest (regardless if it is a function or not).
So having:
$scope.bindFun = true;
vs
$scope.bindFun = function() { return foo(); };
both are going to be checked in every digest, but certainly having to call two extra functions every time, will reduce the performance. I don't think it is a big deal, not that Javascript takes ages to call a function.
On the other hand, that is a pretty common pattern.

AngularJS, notify data change from directive to its parent controller

I have a directive that abstracts a menu. The menu item selection needs to be notified to its parent controller so it can take required action. There are multiple ways I could get this achieved.
Pass a scope variable from controller to directive and observe the change on this variable. Within the directive change this variable to indicate the selection option.
Pass a callback method from controller to directive. Invoke the callback from directive upon change.
Observe the changes in controller using $scope.$on and notify from the directive using scope.$emit
I could not clearly arrive at which one option is better. I am leaning towards option 3 as it seems to be cleaner but I am not sure if this has a unwanted coupling. I would like to hear an opinion from others, which solution would favour clear dependency and good for testability.
UPDATE:
After reading the suggestions and thought, I picked up Option 2 for below reasons:
It is very obvious by looking into the HTML about the dependency
<menu save="onSave()" filterByDate="filterByDate(date)"></menu>
Unit testing is very explicit and tell about the API (interface) the directive
I personally don't like using observe\emit\on unless i have too. It isn't always obvious looking at someone elses code where it is being set. This can lead to spaghetti code. If you have a call back, i find the direct link is more obvious to the eye and easier to find. This is in conjunction with well named properties etc. At the end of the day it's a matter of personal\team taste.
I recommend using a modified version of 1. But instead of passing a variable, inject a service wherever you need.
The service can look something like this:
yourApp.factory('SelectedMenu',
function () {
//set a default or just initialize it
var menuItem = {
id: 1,
code:"x"
};
return {
getId: function () { return menuItem.id; },
getCode: function() { return menuItem.code;},
setId: function(newId){menuItem.id = newId},
setCode: function(newCode){menuItem.code=newCode;},
};
}
);
In the directive(s) which controlls the behavior create a watch:
$scope.$watch(
function() {
//should be idempotent! can execute multiple times per $digest cycle when a change is detected
// (good compromise for not polluting $rootScope)
return SelectedMenu.getId();
},
function( newValue) {
//do your thing...
}
);
When you need to use it/update the value you can set the code or any other property you want and finally set the id to trigger the update.
SelectedMenu.setId(someones.id);
It is the cleanest solution i came across. If you want, you can abstract the usage even more with another service if the data object is more complex.

Is expression inside ng-show fired after every model change in 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.

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