If I invoke $scope.$apply() ten times in immediate succession I presume ten root scope digests will occur.
Can we say that if the call to $scope.$apply() was debounced so that the trailing call was always completed that the final state of the application would be the same as if the debounce was not in effect?
Can we say anything about the duration of successive root scope digests, given that a previous digest has just completed?
Edit:
I would like to clarify the purpose of my question.
Say I have a controller MyController, with an instance instantiated for each instance of a directive MyDirective.
These controller instances listen for an event my-event and trigger a root-scope digest each time my-event is raised.
Ten instances of MyDirective are rendered to the UI via an ng-repeat.
Another component raises an event my-event. Each of the ten MyController instances then trigger a root-scope digest.
If we put the sanity of this state of affairs to one side, my question is this: if I debounce the root-scope digest attempts made by MyController, and ensure that the trailing attempt always gets invoked, is the correctness of the program maintained?
var service = require('my-service');
function MyController($scope) {
this._$scope = $scope;
myService.on('my-event', this.triggerRootScopeDigest.bind(this));
}
MyController.prototype.triggerRootScopeDigest = function() {
this._$scope.apply();
}
The edited question still points to $applyAsync or $evalAsync as your solution.
Here's an example fiddle comparing both $apply() and $applyAsync():
http://jsfiddle.net/635pvkkt/
You'll notice that 10 items are added via ngRepeat, each watching an doApply event, and an doApplyAsync event, to trigger the respective functions.
When you click the button that broadcasts the doApply, it triggers 10 $digest calls, each doing the directives work (in this case, as simple console.log).
The doApplyAsync broadcast, however, causes all 10 directives to do their work in a single $digest.
Of course, a debounced callback would also work. You could pass each directive a reference to a debounced function that is attached to a parent Controller's scope. If that debounce function works correctly and has a long enough debounce-time, it will only apply once. In some situations that's preferred, but the original question feels simple enough (assuming triggering a $digest is the main goal of the event) that substituting $apply for $applyAsync (or $evalAsync, depending on the semantics) seems more appropriate.
EDIT: Though the results are the exact same, this fiddle is more accurate as it triggers real DOM events on the elements directly: http://jsfiddle.net/uh9wxxho/
Related
We interop our angularJS web components with a jqxGrid. When the user edits in a cell, we create a custom typeahead editor (written in angular). When the editor is destroyed, I noticed that my $watches array doesn't return back to the previous value.
I am creating a new isolateScope for my directive, which I then compile and then append to the DOM element that JQX passes to me when the editor is needed:
var scope = $rootScope.$new(true);
var customEditor = $compile(directive)(scope);
What do I have to do in order to clean up these $watches?
Its likely that the new scope you are creating via
var scope = $rootScope.$new(true);
Is not being destroyed by the jqxGrid once the jqxGrid is done with the editor.
To clean up the watches, you simply need to ensure that a call is made to
scope.$destroy();
The tricky part is figuring out when to execute the destroy call; I believe the jqxGrid should raise events such as beforeEdit and afterEdit which you can subscribe to; the place where the $destroy() call should be made is within an event handler for the afterEdit event.
Here is the way to clean up watchers effectively.
Should angular $watch be removed when scope destroyed?
Hope this helps.
In my controller I have this:
myApp.controller(function(){
var list;
for (var i in data) { // This has more than 5000 objects
list[i] = new MyObject(data[i]);
}
// At this point, it is very fast to populate the list
$scope.list = list;
$scope.$apply() // It is here where it hangs for a long time and freezes the app
})
Is there a way to avoid this? In my view I'm not doing any changes to those objects. I just have to display them.
Since you are manipulating your list within a controller, you don't need to call $scope.$apply().
Angular has made sure of one thing, that all its directives and code
that is wrapped inside of angulars context, a $apply() cycle is called
within the digest loop that it runs continuously.
So in your case, the controller is basically wrapped inside the angulars context, which results in implicit calling of the digest loop which invokes the $apply() function, thereby resulting in your view being updated.
Note: If you wish to call $apply manually, then it would be better if you wrap your list inside of $apply() and invoke it with a 1ms delay, so as to not get a digest loop is already running error:
$scope.$apply(function(
$scope.list = list;
));
For more information you can refer the following link:
Angular JS Apply and Digest Cycle
https://www.sitepoint.com/understanding-angulars-apply-digest/
I'm trying to figure out if angular base automatically unbinds watchers and scope events bound with $scope.$on(...) or $scope.$watch(...) when scope is destroyed?
Suppose I have following code:
$scope.$on('someEvents', handleSomeEvent);
$scope.$watch('someProperty', handleSomePropertyChange);
Do I need to manually unbind these watchers and events when $destroy event is triggered on scope?
According to Angular documentation on $scope:
'$destroy()' must be called on a scope when it is desired for the scope and its child scopes to be permanently detached from the parent and thus stop participating in model change detection and listener notification by invoking.
Also
Removal also implies that the current scope is eligible for garbage collection.
So it seems when $destroy() is called all the watchers and listeners get removed and the object which represented the scope becomes eligible for garbage collection.
If we look at the destroy() source code we'll see a line :
forEach(this.$$listenerCount, bind(null, decrementListenerCount, this));
Which is supposed to remove all the listeners.
As mentioned by #glepretre it applies to the watchers and listeners in the controller. The same doc page listed above says that:
Note that, in AngularJS, there is also a $destroy jQuery event, which can be used to clean up DOM bindings before an element is removed from the DOM.
So if you have specific listeners in the directives you should listen to the $destroy event and do the necessary cleanup yourself
If the scope is in a controller, angular unbind for you. Else you can unbind your event by calling the returned function :
var myevent = $scope.$on('someEvents', handleSomeEvent);
myevent() ; // unbind the event
http://docs.angularjs.org/api/ng/function/angular.bind
As previously answered, Angular indeed takes care of cleaning things for you, whenever possible. So if you do $scope.$on('someEvents', handleSomeEvent);, once the scope is destroyed (eg when you go to another page/view in your app), the event is automatically removed.
One important thing to note though, is that $rootScope is of course never destroyed, unless you quit your app. So if you do $rootScope.$on('someEvents', handleSomeEvent);, you may have to remove the event yourself, depending on where you listen to the event:
if in a controller or directive, then you'll have to remove it manually, else each time you'll instantiate the controller, a new event will be attached, and so handleSomeEvent will be called many times
if in a service, then you do not need to remove it manually, as services are always singleton (note that in Angular service, factory, ... all end up being the same thing)
I need to perform some operations on scope and the template. It seems that I can do that in either the link function or the controller function (since both have access to the scope).
When is it the case when I have to use link function and not the controller?
angular.module('myApp').directive('abc', function($timeout) {
return {
restrict: 'EA',
replace: true,
transclude: true,
scope: true,
link: function(scope, elem, attr) { /* link function */ },
controller: function($scope, $element) { /* controller function */ }
};
}
Also, I understand that link is the non-angular world. So, I can use $watch, $digest and $apply.
What is the significance of the link function, when we already had controller?
After my initial struggle with the link and controller functions and reading quite a lot about them, I think now I have the answer.
First lets understand,
How do angular directives work in a nutshell:
We begin with a template (as a string or loaded to a string)
var templateString = '<div my-directive>{{5 + 10}}</div>';
Now, this templateString is wrapped as an angular element
var el = angular.element(templateString);
With el, now we compile it with $compile to get back the link function.
var l = $compile(el)
Here is what happens,
$compile walks through the whole template and collects all the directives that it recognizes.
All the directives that are discovered are compiled recursively and their link functions are collected.
Then, all the link functions are wrapped in a new link function and returned as l.
Finally, we provide scope function to this l (link) function which further executes the wrapped link functions with this scope and their corresponding elements.
l(scope)
This adds the template as a new node to the DOM and invokes controller which adds its watches to the scope which is shared with the template in DOM.
Comparing compile vs link vs controller :
Every directive is compiled only once and link function is retained for re-use. Therefore, if there's something applicable to all instances of a directive should be performed inside directive's compile function.
Now, after compilation we have link function which is executed while attaching the template to the DOM. So, therefore we perform everything that is specific to every instance of the directive. For eg: attaching events, mutating the template based on scope, etc.
Finally, the controller is meant to be available to be live and reactive while the directive works on the DOM (after getting attached). Therefore:
(1) After setting up the view[V] (i.e. template) with link. $scope is our [M] and $controller is our [C] in M V C
(2) Take advantage the 2-way binding with $scope by setting up watches.
(3) $scope watches are expected to be added in the controller since this is what is watching the template during run-time.
(4) Finally, controller is also used to be able to communicate among related directives. (Like myTabs example in https://docs.angularjs.org/guide/directive)
(5) It's true that we could've done all this in the link function as well but its about separation of concerns.
Therefore, finally we have the following which fits all the pieces perfectly :
Why controllers are needed
The difference between link and controller comes into play when you want to nest directives in your DOM and expose API functions from the parent directive to the nested ones.
From the docs:
Best Practice: use controller when you want to expose an API to other directives. Otherwise use link.
Say you want to have two directives my-form and my-text-input and you want my-text-input directive to appear only inside my-form and nowhere else.
In that case, you will say while defining the directive my-text-input that it requires a controller from the parent DOM element using the require argument, like this: require: '^myForm'. Now the controller from the parent element will be injected into the link function as the fourth argument, following $scope, element, attributes. You can call functions on that controller and communicate with the parent directive.
Moreover, if such a controller is not found, an error will be raised.
Why use link at all
There is no real need to use the link function if one is defining the controller since the $scope is available on the controller. Moreover, while defining both link and controller, one does need to be careful about the order of invocation of the two (controller is executed before).
However, in keeping with the Angular way, most DOM manipulation and 2-way binding using $watchers is usually done in the link function while the API for children and $scope manipulation is done in the controller. This is not a hard and fast rule, but doing so will make the code more modular and help in separation of concerns (controller will maintain the directive state and link function will maintain the DOM + outside bindings).
The controller function/object represents an abstraction model-view-controller (MVC). While there is nothing new to write about MVC, it is still the most significant advanatage of angular: split the concerns into smaller pieces. And that's it, nothing more, so if you need to react on Model changes coming from View the Controller is the right person to do that job.
The story about link function is different, it is coming from different perspective then MVC. And is really essential, once we want to cross the boundaries of a controller/model/view (template).
Let' start with the parameters which are passed into the link function:
function link(scope, element, attrs) {
scope is an Angular scope object.
element is the jqLite-wrapped element that this directive matches.
attrs is an object with the normalized attribute names and their corresponding values.
To put the link into the context, we should mention that all directives are going through this initialization process steps: Compile, Link. An Extract from Brad Green and Shyam Seshadri book Angular JS:
Compile phase (a sister of link, let's mention it here to get a clear picture):
In this phase, Angular walks the DOM to identify all the registered
directives in the template. For each directive, it then transforms the
DOM based on the directive’s rules (template, replace, transclude, and
so on), and calls the compile function if it exists. The result is a
compiled template function,
Link phase:
To make the view dynamic, Angular then runs a link function for each
directive. The link functions typically creates listeners on the DOM
or the model. These listeners keep the view and the model in sync at
all times.
A nice example how to use the link could be found here: Creating Custom Directives. See the example: Creating a Directive that Manipulates the DOM, which inserts a "date-time" into page, refreshed every second.
Just a very short snippet from that rich source above, showing the real manipulation with DOM. There is hooked function to $timeout service, and also it is cleared in its destructor call to avoid memory leaks
.directive('myCurrentTime', function($timeout, dateFilter) {
function link(scope, element, attrs) {
...
// the not MVC job must be done
function updateTime() {
element.text(dateFilter(new Date(), format)); // here we are manipulating the DOM
}
function scheduleUpdate() {
// save the timeoutId for canceling
timeoutId = $timeout(function() {
updateTime(); // update DOM
scheduleUpdate(); // schedule the next update
}, 1000);
}
element.on('$destroy', function() {
$timeout.cancel(timeoutId);
});
...
I have scenario where I want to send a broadcast event in one controller and have the controller for a directive receive the message. The event is sent immediately on controller startup, and the issue is that because the directive is loading the view using templateUrl, it happens asynchronously, so the event is broadcast before the directive controller is intialised. This problem doesn't happen if the view is in the main page body, but I guess there could still be an issue with controller initialisation order.
I am using the following code in my main controller:
$rootScope.$broadcast("event:myevent");
I have reproduced the issue here: http://jsfiddle.net/jugglingcats/7Wf8N.
You can see in the Javascript console that the main controller is initialised before the controller for the directive, and it never sees the event.
So my question is whether there is a way to wait until all controllers are initialised before broadcasting an event?
Many thanks
I have created a working version. I actually feel that it is a very unclean way to do it, but I could not come up with something better: http://jsfiddle.net/7Wf8N/3/
What I did is this: In the directive I added some code which will increase a counter in $rootScope upon initialization. I use a counter because as you said, you want to wait for more than one controller:
$rootScope.initialized = ( $rootScope.initialized||0 ) +1;
In the "RegularCtrl" I added a watch on this counter and if the counter reaches the correct value (everything is initialized) I send the event:
$rootScope.$watch('initialized', function() {
if ( $rootScope.initialized == 1 ) {
$rootScope.$broadcast("event:myevent");
}
});
Are you ng-view? If so, you have the $viewContentLoaded event available. This will fire after all the dom is loaded.
http://docs.angularjs.org/api/ngRoute.directive:ngView
function MyCtrl($scope, $rootScope) {
$scope.$on('$viewContentLoaded', function() {
$rootScope.$broadcast('event:myevent');
});
}
If you aren't using ng-view, you could just set a variable and use data-binding to your directive.