I am new to AngularJS. Please see the code below and tell me what it is doing.
$scope.$on('$viewContentLoaded', function(event) {});
How to use it in a controller to access the DOM?
$timeout(function() { });
I am looking for explanation and example of how to use $scope.$on() and $timeout() in real life and what it does.
$scope.$on registers a listener for the event passed as the first parameter and executes the function passed as the second on each instance of said event. $broadcast and $emit can be used to send out custom events of your own.
$timeout can be used in place of setTimeout but when called with no delay argument will simply wait for the next digest before executing its callback function.
As for DOM manipulation, this should not be carried out in a standard 'jQuery like fashion'. If manipulation of the DOM is required a custom directive can be defined to encapsulate this functionality and therefore allow the Angular framework to govern its syncopation.
Related
I am reading
http://angular-tips.com/blog/2013/08/watch-how-the-apply-runs-a-digest/
directive link function:
element.bind('click', function() {
scope.foo++;
scope.bar++;
scope.$apply();
});
a better way for using $apply:
element.bind('click', function() {
scope.$apply(function(){
scope.foo++;
scope.bar++;
});
});
What’s the difference? The difference is that in the first version, we are updating the values outside the angular context so if that throws an error, Angular will never know. Obviously in this tiny toy example it won’t make much difference, but imagine that we have an alert box to show errors to our users and we have a 3rd party library that does a network call and it fails. If we don’t wrap it inside an $apply, Angular will never know about the failure and the alert box won’t be there.
Confusion:
Why angular need to know error, i just need to show it for users. for example, there is an ajax request in link fn of directive, I just need to tell what happened if fails.
TAngular $scope has a function called $apply() which takes a function as an argument. AngularJS says that it will know about model mutation only if that mutation is done inside $apply(). So you simply need to put the code that changes models inside a function and call $scope.apply(), passing that function as an argument. After the $apply() function call ends, AngularJS knows that some model changes might have occurred. It then starts a digest cycle by calling another function —- $rootScope.$digest() — which propagates to all child scopes. In the digest cycle watchers are called to check if the model value has changed. if a value has changed, the corresponding listener function then gets called. Now it’s upto the listener how it handles the model changes.
The Ajax call through Angular buildin $http the model mutation code is implicitly wrapped withing $apply() call, so you don’t need any additional steps.
At some point after a user action I would like to cause a digest to occur, so the UI reflects a change in the data-model backing it.
I have a service that performs some change in a callback (asynchronously).
I understand that $scope only makes sense in the context of a controller. Can I achieve the same effect by performing $apply() on the $rootScope?
I have seen code that checks for $$phase or similar related to avoiding digest errors, what checks should I perform in order to trigger a digest safely?
See this answer: Running $apply on $rootScope vs any other scope
You can call $rootScope.$apply() outside of a controller (i.e. in a service) in order to trigger a digest loop.
Alternatively, you could consider using $broadcast and $on to send a notification to other parts of your app when something needs refreshing. (See Understanding Angular’s $scope and $rootScope event system $emit, $broadcast and $on)
// in a service
$rootScope.$broadcast('myCustomEvent', {
someProp: 'foobar'
// other data
});
// in a controller or another service
$scope.$on('myCustomEvent', function (event, data) {
console.log(data);
// do something with this event
});
I am reviewing someone else's code and I see that they have $scope.$apply in their directive.
The scenario is that we got some event from the DOM and we want to change the scope.
From my experience, directives should call apply. It causes some weird side effects.
One of them is in the directive's test. All my tests have the same pattern
$compile( "<the html>" )(scope);
scope.$digest(); --> will error if directive calls apply
Should directives call apply?
What is the recommended solution for apply changes on scope when you get an event from DOM which is not wrapped in angular?
I would say that calling $scope.$apply or $scope.$digest is usually (although not always) a bad idea.
For your example, registering DOM event can go through angular using ng-click, ng-keydown, etc, which will conceal the need to call $apply or $digest.
The reason it is even needed, is obviously because there is some code executed "outside" angular, meaning, outside of the angular ecosystem and so basically angular doesn't "know" an event (or any other data related thing) has happened.
So to sum up, there should be a (very) good reason to call $apply or $digest.
How else?
Well, you could encapsulate these event capturing inside your own directive (although most if not all of them are covered on angular). These is exactly what angular itself does and will result $apply or $digest only when actually needed by the event itself.
/EDIT/
For instance, a simplified version of angular's ng-click can be translated into your own directive:
app.directive('myClick', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var clickHandler = $parse(attr.myClick);
element.on('click', function(event) {
// Do some of your own logic if needed.
scope.$apply(function() {
// Calling the event handler.
clickHandler(scope, {$event: event});
});
});
}
}
}]);
By encapsulating this event handler, it can be reused (in this a form of a directive) and because being part of angular's world, any other logic using this directive, doesn't have to worry about $apply or $digest. It also means it can be used declaratively now (rather then operatively) which is what angular aspires anyway.
One thing to notice, this directive doesn't isolate its scope and doesn't introduce any other new variables on the scope (the event handler is being parsed on the linking function). This is important because it means there are is no overhead side effects on the parent scope (the scope that needs to "know" about this event - which is basically the main scope), since the directive's scope is inherited.
P.S You can also consider overriding directives or decorating other services on angular.
Well... if your directive wrapps some native events or anything outside of the Angular scope, you dont have much more options than calling "$apply()". From my experience this will only cause an error if this function is called from both, WITHIN the angular scope and from outside (e.g. ng-click as well as window-click event or something). If this is a case, you can still use the $timeout-Service. It's not the nicest solution, but from what I`ve heard its even the suggested one from the angular team.
I'm very confused when a digest cycle is happening, is it called periodically based on a timer every 50ms (as it says here and implied here) or is it called after every event that enters the angular context (as it says here, here and here) ?
Example when it is matter:
In my model, I have a variable called myVar with the value of 3.
In my HTML, I have {{myvar}}.
An event such as a button click is fired and raises a handler in the controller, the code inside the handler is:
$scope.myVar = 4;
// some heavy actions takes place for 3 seconds...
$scope.myVar = 5;
Assuming the UI thread is not blocked, what will the user see after the button click? will he see only 5 or will he see 4 and after 3 seconds 5?
I think the description of the digest cycle at http://blog.bguiz.com/post/60397801810/digest-cycles-in-single-page-apps that it is
code that runs at an interval
is very misleading, and to be honest, when referring to Angular, I would even say wrong. To quote Pawel Kozlowski, Mastering Web Application Development with AngularJS
AngularJS does not use any kind of polling mechanism to periodically check for model changes
To prove there is no polling, if you have a template of
<p>{{state}}</p>
and controller code of
$scope.state = 'Initial';
// Deliberately *not* using $timeout here
$window.setTimeout(function() {
$scope.state = 'Changed';
},1000);
as in this plunker, then the string shown to the user will remain as Initial and never change to Changed.
If you're wondering why you often see calls to $apply, but not always, it is probably because the various directives that come with Angular, such as ngClick or ngChange will call $apply themselves, which will then trigger the cycle. Event listeners to native JS events directly will not do this, so they will have to deliberately call $apply to have any changes made reflected in templates.
The digest process is kicked-in when any of the following occur as part of angular context:
DOM Events (like ng-click etc.)
Ajax with callbacks ($http etc.)
Timers with callbacks ($timeout etc.)
calling $apply, $digest
etc.
It is important to note that the normal browser related DOM events (onclick etc.) and setTimeout would not trigger a digest process as they work out of "Angular Context".
I learnt the above from the following:
The above is a quick snapshot from a very in-depth tutorial available here: https://www.youtube.com/watch?v=SYuc1oSjhgY
Any AngularJS scope variable when handled from outside (including ajax) needs a $apply().
setTimeout is Javascript function So $apply is needed to update angularjs values.
$timeout is a angularjs function which returns promise and takes care of the current scope and runs in the same digest cycle.
So need not of $apply() function to update values.
Is it recommended to always hang on to the $on method, as in
var dereg = $scope.$on(...)
And later
$scope.$on("$destroy", function() { dereg(); });
Or is this only needed for certain situations? Same question for $watch
AFAIK, you should only use $destroy event when your app may have memory leak issues or zombie events.
$destroy is useful when you pass some of the directive's variables to another directive or controller, and after the element gets removed, yo want to remove it from the reference.
For instance, you may want to use $destory when your directive creates event handlers on global DOM elements and the element with the directive gets removed.
let's say this is a linking function inside a directive:
function myEventListener(){console.log('scroll!');}
$window.bind('mousewheel',myEventListener);
scope.$on('$destroy',function(){$window.unbind(myEventListener)});
If you don't unbind your event listener, then after the element with the directive gets removed, you will still have scroll! messages when you scroll.