I've been playing around a bit with ES6 and angular and I'm using eslint-plugin-angular to validate my javascript. I have the following service:
export function runBlock ($rootScope, $state, $log) {
'ngInject';
$rootScope.$on( '$stateChangeStart', function(event, toState) {
// ...
} );
But eslint gives me the following error:
The "$on" call should be assigned to a variable, in order to be
destroyed during the $destroy event
I mean I understand the warning, but I've never done this in my previous angular projects, should I have done what the error suggests? Why is it needed/good practice?
The docs for eslint-plugin-angular reference John Papa's angular styleguide, but I didn't really find a mention of this situation there.
Not only does the johnpapa styleguide not mention this situation, it actually includes an example of ignoring the return of $rootScope.$on. However, the discussion on one of the eslint-plugin-angular issues clarifies the intent a little bit:
If a controller is registering a listener on $rootScope it should probably be manually destroyed in "$destroy" since root scope will outlive all the controllers. --davidmason
That post also indirectly references the "Directives should clean up after themselves" best practice from the AngularJS documentation.
So bottom line: A regular $scope object will eventually be destroyed when its controller does, and take its event listeners with it (assuming you haven't done any kind of circular referencing that keeps it in scope). $rootScope never dies, and therefore never releases its event handlers. If your controller is adding an event listener to $rootScope, it should remove that handler in your controller's $destroy handler.
Related
I'm trying to figure out what would be the best way to perform cleanup on different angular directives.
I have different types of directives, some do not define their own scope, some have an isolated scope, and some have a child scope.
I need a generic mechanism that will take care of the cleanup in a separate component that my directives use.
So basically I'm looking at two different options, either register on angular's element.on('$destroy', function() {...}) or on jquery's scope.$on('$destroy', function () {...}).
Here's the problem:
If I register on the underlying element destruction then I miss destruction of directives that they're element was not destructed (not sure exactly how's that possible, noticed it via testing...).
If I register on the underlying scope destruction then (I think) I miss destruction of directives that they're element was destructed, for instance when the directive is not defining its own scope and is using its parent scope.
Looking at angular's directives documentation I came across this:
Best Practice: Directives should clean up after themselves. You can use element.on('$destroy', ...) or scope.$on('$destroy', ...) to run a clean-up function when the directive is removed.
There's no mention as far as I can tell about which strategy to use when.
Also, looking at Angular's documentation I came across this:
When child scopes are no longer needed, it is the responsibility of the child scope creator to destroy them via scope.$destroy() API.
I don't understand the scenario in which I am supposed to call the scope.$destroy() API on my own.
I have an application that exposes an methods (from various angular services) to code that is loaded and eval'd at runtime. What is the best way to guarantee $digest triggers when these methods are called from outside angular, taking into account that they might also be called from within angular? Should I expose a separate interface wrapped in $scope.$apply instead of exposing the service methods directly?
Quick solution would be to call $evalAsync. From documentation:
" if this function is called outside of a $digest cycle, a new $digest cycle will be scheduled. " https://docs.angularjs.org/api/ng/type/$rootScope.Scope
My advice to expose something outside Angular is to expose element (by providing ID or selector) where you have scope or (better) controller attached and then use 'angular.element(someDomEkenent).scope().someFunction()'
In this case you would avoid possibility to capture unessesary objects that can lead to memory leaks that is very hard to debug
call $timeout() function with no time.
for example
function someEventOutsideAngualer(value) {
$timeout(function(){
$scope.bindedValue = value;
})
}
The idea is that when $timeout callback is executed, in our case it will be picked up with the next $digest. Please do not use $scope.$apply() and $digest triggers. They cause more problems than they solve, if not properly understood
Suppose this is my angular controller
app.controller("MyCtrl", function($scope, $modal, $state,) {
});
I am thinking of having one global variable holding most commonly used dependencies like
var all = ['$scope', '$modal', '$state']
and then use all at every place along with some other dependencies if needed
Is there any performace issue having put all dependencies everywhere
Having to inject more code would have a performance hit, but not a major one. I don't recommend defining your dependencies globally like that because dependencies should be very visible. You should know exactly what you're doing with them without having to open another file and check.
If you need to reuse a set of dependencies everywhere, that suggests more that there's probably something wrong with the code. How come different regions of the code base all talk to the same stuff? That suggests duplication of concerns. I don't extend that assertion to just having to inject $scope or $http all the time.
In short, I don't think it's a good idea to manage dependencies like that.
While I don't have deep knowledge of the Angular internals, nor have I bothered to actually measure the performance of what you are asking, I would venture my educated guess as .... no. You're not going to see a performance impact here. The only impact would be on controller instantiation, which only happens once per view. And even then, we're just talking about new-ing up a few objects ... the perf impact ought to be very negligible, and not something I would worry about.
You can't inject using a variable you define within another controller or service. One thing you could do is create a factory and put your dependencies in the $rootScope.
app.factory('root',function($rootScope, $modal, $state){
$rootScope.modal = $modal;
$rootScope.state = $state;
});
You just put $rootScope in all your controllers and you have access to whatever you like. You would only need to inject 'root' in your main controller (if you have one). It seems like fishy architecture to need this shortcut, but that is how I would do it. No perf hit really - non-primitives are reference types.
My Angular application is fairly big already and I am using the rootScope to communicate between directives and controllers.
Directive 1
scope.$root.$broadcast('some:event');
Controller 1
$rootScope.$on('some:event', function() { I get called multiple times :( });
$scope.$on('some:event', function() { I am not getting called at all :( });
For some reason, my listeners get called multiple times (2x to be exact). I have the feeling that somewhere, I built in a second rootScope or something. I am currently debugging my app, but it is like finding the needle in the haystack.
This thread tries to solve a similar problem: AngularJs broadcast repeating execution too many times. It suggests to use $scope only which does not work for me in my particular case. The broadcasted events never reach the listeners.
My question would be if someone has an idea why this could happen? Maybe I am doing a silly mistake I am not aware of. Catching an event twice I only sent once does sound wrong.
Thanks in advance
use
$rootScope.$emit('some:event') ;
because it goes upwards and rootscope ist the top level
use
var myListener = $rootScope.$on('some:event', function (event, data) { });
$scope.$on('$destroy', myListener);
dont forgot to unregister on destroy and u have a bus communication on the same level
Edit: To your problem: maybe some other listener prevent it from bubbling further like this:.
$scope.$on('some:event', function (event, data) {
event.stopPropagation();
});
And the reason why your listener receive mulltible times could be that u send multible times. test with sending a timestring with your event and see if it is the same
Here is a fiddle with my Eventbus factory i use http://jsfiddle.net/navqtaoj/2/
Sometimes the good old observerpattern works also fine:http://jsfiddle.net/b742qdpz/ if it is to much overhead to watch a value in a service
Tighten Your Module & Scope
Many times, when you're experiencing multiple dispatches of an event -- or more specifically, when you've multiple Handlers being invoked then your modules need to utilize locality more.
By locality I mean, try not to use globals as much as possible. This can (and usually does) occur, usually, when you're listening/firing events off of $rootScope instead of $scope -- which is more local.
What Angular Does (In a Nutshell)
Angular has a special way of event-subscription-binding which differes based upon scope.
If an event is subscribed to using a controller, which uses a perishable scope, then Angular automatically unsubscribes to each event when the controller closes.
However, when subscribing using $rootScope, Angular will not insist on detaching these subscription-bindings. So...
TL;DR
Every time you load a view -- effectively constructing a new controller -- you are undoubtedly wiring up another listener for each subscription.
Hope this helps!
I see this type of code a lot in angular modules
scope.$on('$destroy', function(){
//undind listener here
});
My understanding is that whenever the scope is about to be destroyed it broadcasts a $destroy event allowing you to clean up any code that may continue to run after the destruction of the scope which would create memory leaks.
My question is, when does the scope naturally get destroyed in an angularjs app. All the documentation I can find from the website is that you can manually call $destroy to remove a scope, but this seems to suggest that it will happen at some point automatically. When would that be?
Scope is tied to HTML elements during compilation. $compile needs a scope to compile an element. Elements could be nested. Some get new scope other inherit.
Scope gets destroyed when elements are removed from DOM.
To be precise: A $destroy handlers are called on jQuery.cleanData which AngularJS redefines and calls after it does its cleanup - aka acting in destroying the scope.
cleanData function is called when an element is removed from the DOM.
What is the purpose of jQuery clean and cleanData methods?