I am trying to understand the lifetime of scopes in Angular and was wondering if doing $rootScope.$on or $rootScope.$watch ever goes out of scope (in the traditional sense)?
Does Angular have any type of Garbage Collection or anything like that?
Do you have to worry about releasing objects ($scopes)?
The entirety of angular scopes is a rather large piece of documentation, but you can start here or here.
The short, lofty answer is that angular inspects your modules (angular.module('myapp')) to create a complex tree of dependencies. Then the DOM is parsed to identify "ng" tags or other custom directives, which go through a compile phase (that constructs the custom HTML) and a linking phase (that generates a scope and attaches it to that element). That is about 1/100th of what really goes on, but it's a start.
Angular does perform garbage collection, but almost all of it happens behind the scenes. When a $scope is scheduled to be removed, one of the last things it does is fire a $scope.$destroy(), which you can hook into with $scope.$on('$destroy', function() { .. }) to perform any cleanup. Then again, in most instances you don't have to worry about cleanup or "releasing" anything, as angular takes care of that as well.
:Edit:
I also stumbled across this, which explains it best by far-- understanding scopes.
Related
Pardon me if this sounds stupid but I have been using AngularJS for a while now and everywhere I have seen people telling me to wrap my logic in a directive(or service ?) instead of my controller and keep only the bindings in my controller. Apart from the reusability aspect of a directive is there any other reason ?
Until now I haven't actually understood why this is the case. Doesn't writing a directive come with a lot of overhead ? I haven't faced any kind of problems writing logic in my controller and it is EASY. What ARE the downfalls of this approach ?
The controller is the right place to do all and everything that is related to the scope. It is the place where you write all the
$scope.$watch(...)
and define all the $scope functions that you need to access from your views ( like event handlers ). Generally, the event handler is a plan function which will in turn call a function a service.
$scope.onLoginButtonClick = function(){
AuthenticationService.login($scope.username,
$scope.password);
};
On very rare occasions you can add a promise success handler in there.
DONT: Write business logic in controllers
There was a very specific reason why the earlier example was like that. It showed you a $scope function that was in turn calling a function in a service. The controller is not responsible for the login mechanism or how login happens. If you write this code in a service, you are decoupling the service from the controller which means anywhere else that you want to use the same service, all that you need to do is, inject and fire away the function.
Rules for the Controller going forward:
Controllers should hold zero logic
Controllers should bind references to Models only (and call methods returned from promises)
Controllers only bring logic together
Controller drives Model changes, and View changes. Keyword; drives, not creates/persists, it triggers them!
Delegate updating of logic inside Factories, don't resolve data inside a Controller, only update the Controller's value with updated Factory logic, this avoids repeated code across Controllers as well as Factory tests made easier
Keep things simple, I prefer XXXXCtrl and XXXXFactory, I know exactly what the two do, we don't need fancy names for things
Keep method/prop names consistent across shared methods, such as this.something = MyFactory.something; otherwise it becomes confusing
Factories hold the Model, change, get, update, and persist the Model changes
Think about the Factory as an Object that you need to persist, rather than persisting inside a Controller
Talk to other Factories inside your Factory, keep them out the Controller (things like success/error handling)
Try to avoid injecting $scope into Controllers, generally there are better ways to do what you need, such as avoiding $scope.$watch()
There two good reasons for me for keeping logic out of a controller:
Reusability
If your application has multiple controllers and each do pretty much the same thing with some differences then keeping logic in the controller means you will be repeating the code you write. It's better if you Don't Repeat Yourself. By putting that logic into a service you can inject the same code into multiple controllers. Each service (really a Factory) is created as a new instance of itself each time it is injected into a controller. By pushing logic into a service you can modularise your code, which keeps it easier to maintain and test (see below)
Testing
Good code is tested. Not just by people but by the unit tests you write. Unit tests give you as a developer assurance that your code does what you expect it too. They also help you design your code well.
If your controller has 20 different methods each with their own logic, then testing (and your code) is turning into spaghetti.
It's easier to write unit tests that are narrow i.e. they test one thing at a time. And fortunately it's also good (for the reasons outlined above) to break your code up into encapsulated pieces i.e. they do one thing and can do it in isolation. So unit tests (especially if you write your tests first) force you into thinking about how to break up your code into maintainable pieces, which leaves your application in a good state if you want to make changes in the future (you run the unit tests and can see where things break).
Example
Form application:
You have a form application serving multiple forms. You have a controller for each form. When the user submits the form the data is sent via a proxy to a CRM that stores the information in a database.
If a customer already exists in the CRM you don't want to create duplicates (yes the CRM should handle data cleansing but you want to avoid that where possible). So once the user submits their form data something needs to implement logic that goes something like:
search for the user in the CRM via an API endpoint
if the user exists get the user ID and pass it with the form data to another endpoint
if they don't exist hit another endpoint and create a new user, get the user ID and send it and the form data to associate it with the user
NB: Arguably all of the above should be done by a back-end service but for the sake of example let's go with it.
Your application has multiple forms. If you hardcode the same logic in each controller for each form (yes you should have a controller per form i.e. one per view) then you are repeating yourself multiple times. And writing tests that need to check the controller can do the basics (post data, manage changes to the view) but also test all of that of that logic for each controller.
Or instead write that logic once, put it in a service, write one test for it and inject it wherever you like.
References
Look at the Angular documentation, and look at the patterns that Angular implements and why these are good to follow (Design Patterns - the big ones being modular, dependency injection, factory and singleton).
The biggest problem with controllers is that you don't define the html it works on.
When you use...
<div ng-controller="myController"></div>
... then you have to inject your html in your controller, which is basicly old fashioned jQuery thinking.
When you use...
<div ng-controller="myController">... some html ...</div>
... your directive and your html it works on are defined in different places. Also not what you want.
Using directives forces you to put your piece of html and the code that it needs in the same place. Because the directive also has it's own scope, there will not be any interference with other variables elsewhere in your code. If you need variables from elsewhere you also have to explicitly inject them, which is also a good thing.
The word I use for why this is a good thing is 'atomic' but I'm not sure if this is the right word. Meaning: all the things that should work together are in one file. With templateUrl this isn't exactly true anymore, still the template is defined in the directive.
So in my controllers there is no code that does anything with the dom. Just the bare minimum like some page/view counting code, or connecting API data to the scope, or doing something with $routeParam data. All other code is put in either Services/Factories (business logic) or Directives (dom logic).
BTW: it is possible to define a controller for your directive, but is normally only used for 'inter-directive communication' (so they can share state), but you only use this with directives that always work together (like a tab directive that is repeated inside a tabs directive).
The main reason why you don't write logic in controllers is all $scopes in the controller get garbage collected with $destroy() on route changes. In the ngView directive when a $routeChangeSuccess broadcast is received, there is a function that only keeps $scope for the currently active view, all other $scopes are destroyed.
So for example, if you have a shopping cart app and your business logic is the controller using $scopes, the user will lose the product and all form data already entered on the order page, if they use the back button, etc.
I am coming to you at great despair - been working on this for two days now.
We have a Single Page Application built with Angular JS. We use the $routeProvider in HTML5 mode to achieve the SPA routing. Functionality wise - all works great!
We have one global controller attached to the body element, there is one controller in the header for a quick search functionality and all other controllers are scoped to a route. There is some data shared between controllers like a currentUser object and a ViewRes object that contains string values for user's chosen language.
But, we noticed that the Chrome service takes too much RAM for our page. I used Chrome Profiles tool to see what is happening. I disabled most of our code that was using a complex directive and left out only the basics. Memory consumption was lowered a lot, but it is still obviously there. Any time I change a page, the memory increases.
In the Heap Snapshot it shows that most memory is taken by (closure) and (array). Also the Detached DOM tree is big. Please note that these snapshots are with bare elements of our application (header, footer and lightweight content). If I include our complex UI components, then memory jumps from 14MB to 50MB to 140MB... and more. Obviously we will take care for these directives, but I am concerned that our issue is global, not just to bad design of the directives.
When I open the (array) element, I notice there are bunch of them with both shallow size and retained size of 6172. Tracking the scope of that object always leads to some ng directive, like ngShow, ngIf...
As you can see from the image, the tree ends at 'cache in function()'. We use Angular 1.3.6.
EDIT: This project also includes jQuery. We were using jQuery 1.8.2 and when I switched to 1.11.2, switching between simple pages (simple ng-repeats and simple models) doesn't cause memory leaks anymore (no more detached DOM elements).
Now the complex directives are still giving me too much detached elements, so I am going to work on those now and I'll post the results here when I find out the cause.
Difficult to say what your specific problems are, but common places for memory leaks in Angular include $interval, $watches and event handlers. Each of these functions creates a closure that is not cleaned up unless you explicitly delete it upon controller tear-down.
$interval in particular is nasty, as it will continue to run until you close the browser or the Web page - even if the user moves to a different tab or application, it won't stop running!
If you create references to DOM elements within these closures, you'll soon start chewing through memory, as the references are never released and the DOM tree becomes detached as the user moves from page to page.
To resolve this, ensure you handle the $destroy event in your controller (and your directives' controllers or link functions), and explicitly clean up after any intervals, watches or event handlers have been used.
You can do this by holding a reference to each $interval, watch or event handler and simply calling it as a function in the $destroy event handler.
For example:
// eventListener to remove
var eventListener = $scope.$on('eventName', function(){…});
// remove the eventListener when the $destroy event is fired
$scope.$on('$destroy', function(){
// call the value returned from $scope.$on as a function to remove
// the event listener
eventListener();
}
// remove an event listener defined on a DOM node:
var elementEventListener = element.on('eventName', function(){…});
element.on('$destroy', function(){
elementEventListener();
}
// Stop an interval
var stop = $interval(function(){...});
$scope.$on('$destroy', function(){
stop();
}
// Finally, unbind a $watch
var watchFn = $scope.$watch('someValue', function(newVal){…}
$scope.on('$destroy', function(){
watchFn();
}
Finally, NEVER store DOM elements in the scope! (see Point #2 here for the reason why).
Use case
For use in a form, I created a directive that tracks changes in an array. It allows changes to be reverted and deletions and additions to be stored separately. It allows for an array (one to many mapping in the database) to be updated incrementally (rather than requiring the server to either diff, or rewrite the entire list).
Problem?
My question is about the way I expose the functionality to the controller's scope. I currently use an two-way databound attribute on the directive's scope. This works, and it seems reliable (of course you can easily break it by reassigning the scope's value, but intentionally you can break anything).
Code
You can see this plunk to see this in action. It allows methods on the directive's controller to be called from the view and the view's controller. (I am using the directive controller intentionally because that's what I do in my actual code for the directive to directive communication, but this could also just be placed in the linking function.)
Question
Is this way of doing it bad design? Am I completely throwing AngularJS out of the window now and hacking in my own code. Are there any better ways to expose functions from a directive (keep in mind that there'll be multiple of these in a single form).
It's very easy to pass in my-attribute="someFunction()" to have the directive be a consumer of the view controller. I can't find a better way to do the opposite and have the view controller consume from the directive.
Alternative?
I've been thinking about using a service here, in which the service will provide an object that is instanciated in the view, passed to the directive, and have the directive blurp out it's results to that object. Then in turn have the view controller consume the information from that service's object. Would this be a better approach?
There's nothing wrong with your approach. In fact built-in angular directives such as ng-form use this approach to store the controller in the scope (see the name property of ng-form) http://docs.angularjs.org/api/ng.directive:ngForm
For more re-usability though I would put the api methods on the controller and then put the controller itself in the api:
this.getChanges = function () {};
this.resetChanges = function(){};
$scope.api = this;
In directives, the main purpose of the controller is to serve as an api for other directives (if you didn't need an api for other directives you could just do everything in the link function). Doing it this way ensures the api is available both on the scope as well as to any directive that 'requires' the oneToMany directive.
I'm using the router (was using the built in one, now using ui-route but solutions for either are fine) in Angular.JS to switch between control/template pairs. When switching back and forth between a couple of these pages it takes up to a second to setup the DOM each time which looks terrible. Is there anyway of having angular keep around the DOM tree instead of recreating it each time. I guess I'd like to just hide/sow the bits for each page rather than remove/re-create them each time.
Any suggestions welcome!
You have to write your own ng-view directive to create such functionality.
The basic idea behind it is:
Before the route changes, instead of destroying the current view element, and scope, you just put it in an invisible cache DIV, and deregister scope listeners. Give the element a data attribute with the $$route.templateUrl to be able to get it back.
Then you fetch the next view from the server.
Before the route changes you check for cache item existance, and if it is in your cache div get the element from the cache, re-register listeners and put the current view the cache.
The tricky part is not to mess the $scopes up. So you might need a constructor and destructor in your $scope for the event, and maybe for the $watchers as well. I'm not sure.
But to be honest, if you using the template cache and still takes 1 second or so to render, then you might have some inefficient $watch expression, or a huge ng-repeat. You should consider some refact.
I've been researching this myself. I am working on rather old hardware in an unaccelerated browser. Initial render is a problem. My early work-around was to cache pre-compiled templates, as you are attempting. I found that this provided only a minimal speed improvement.
The real bottleneck was coming from my ng-repeat directives, the number of reflows that resulted and the number of DOM nodes/watchers I was building in each iteration.
Some sources have suggested creating a custom directive which manually assembles the dom and appends it in one go. The result is very minimal reflow and no watchers. The downside is very large. No more angular fun and a lot of unnecessary work. More importantly, none of this provided a large enough speed improvement to justify the work.
I ultimately found that the best speed improvement came from forcing hardware acceleration for each ng-repeat iteration. As suggested above, the ng-animate directive in newer versions angular make this relatively trivial.
You will see immediate page render, with minor reflow hiccups. ng-cloak does not help here. Due to the animation request, the page is not suposed to be cloaked while the repeat renders. These can, however, be rendered reasonably well with a bit of clever fun. I'm actually hiding the ng-repeat until the $location changes, showing a progress indicator in the meantime, and then toggling my ng-show. This works really nicely.
Having said all of that, precompiling your templates should be done as follows.
1) When your app starts, create a new cache for yourself. See http://docs-angularjs-org-dev.appspot.com/api/ng.$cacheFactory
2) Populate this cache with compiled templates. Inject $compile and call it on each template. Compile returns a function which you will later call against your scope. Key this function in your cache as you see fit.
3) Create a custom directive which accepts a cache key as an attribute. Inside this directive, query your compile cache for the correct compile function. Call the function against your current scope, and append the resulting DOM to the element passed into the directive.
4) Sorta win :).
If you move up to Angular 1.1.5, you can use the ng-animate attribute on you ng-view tag.
I'm not 100% sure, but I think it does some DOM caching to make the transitions work better. You could try adding adding ng-animate attribute to your tag. That might take care of it for you.
I'm looking for some guidelines that one can use to help determine which type of scope to use when writing a new directive. Ideally, I'd like something similar to a flowchart that walks me through a bunch of questions and out pops the correct answer – no new new scope, new child scope, or new isolate scope – but that is likely asking for too much. Here's my current paltry set of guidelines:
Don't use an isolated scope if the element that will use the directive uses ng-model
See Can I use ng-model with isolated scope? and Why formatters does not work with isolated scope?
If the directive doesn't modify any scope/model properties, don't create a new scope
Isolate scopes seem to work well if the directive is encapsulating a set of DOM elements (the documentation says "a complex DOM structure") and the directive will be used as an element, or with no other directives on the same element.
I'm aware that using a directive with an isolated scope on an element forces all other directives on that same element to use the same (one) isolate scope, so doesn't this severely limit when an isolate scope can be used?
I am hoping that some from the Angular-UI team (or others that have written many directives) can share their experiences.
Please don't add an answer that simply says "use an isolated scope for reusable components".
What a great question! I'd love to hear what others have to say, but here are the guidelines I use.
The high-altitude premise: scope is used as the "glue" that we use to communicate between the parent controller, the directive, and the directive template.
Parent Scope: scope: false, so no new scope at all
I don't use this very often, but as #MarkRajcok said, if the directive doesn't access any scope variables (and obviously doesn't set any!) then this is just fine as far as I am concerned. This is also helpful for child directives that are only used in the context of the parent directive (though there are always exceptions to this) and that don't have a template. Basically anything with a template doesn't belong sharing a scope, because you are inherently exposing that scope for access and manipulation (but I'm sure there are exceptions to this rule).
As an example, I recently created a directive that draws a (static) vector graphic using an SVG library I'm in the process of writing. It $observes two attributes (width and height) and uses those in its calculations, but it neither sets nor reads any scope variables and has no template. This is a good use case for not creating another scope; we don't need one, so why bother?
But in another SVG directive, however, I required a set of data to use and additionally had to store a tiny bit of state. In this case, using the parent scope would be irresponsible (again, generally speaking). So instead...
Child Scope: scope: true
Directives with a child scope are context-aware and are intended to interact with the current scope.
Obviously, a key advantage of this over an isolate scope is that the user is free to use interpolation on any attributes they want; e.g. using class="item-type-{{item.type}}" on a directive with an isolate scope will not work by default, but works fine on one with a child scope because whatever is interpolated can still by default be found in the parent scope. Also, the directive itself can safely evaluate attributes and expressions in the context of its own scope without worrying about pollution in or damage to the parent.
For example, a tooltip is something that just gets added; an isolate scope wouldn't work (by default, see below) because it is expected that we will use other directives or interpolated attributes here. The tooltip is just an enhancement. But the tooltip also needs to set some things on the scope to use with a sub-directive and/or template and obviously to manage its own state, so it would be quite bad indeed to use the parent scope. We are either polluting it or damaging it, and neither is bueno.
I find myself using child scopes more often than isolate or parent scopes.
Isolate scope: scope: {}
This is for reusable components. :-)
But seriously, I think of "reusable components" as "self-contained components". The intent is that they are to be used for a specific purpose, so combining them with other directives or adding other interpolated attributes to the DOM node inherently doesn't make sense.
To be more specific, anything needed for this standalone functionality is provided through specified attributes evaluated in the context of the parent scope; they are either one-way strings ('#'), one-way expressions ('&'), or two-way variable bindings ('=').
On self-contained components, it doesn't make sense to need to apply other directives or attributes on it because it exists by itself. Its style is governed by its own template (if necessary) and can have the appropriate content transcluded (if necessary). It's standalone, so we put it in an isolate scope also to say: "Don't mess with this. I'm giving you a defined API through these few attributes."
A good best practice is to exclude as much template-based stuff from the directive link and controller functions as possible. This provides another "API-like" configuration point: the user of the directive can simply replace the template! The functionality all stayed the same, and its internal API was never touched, but we can mess with styling and DOM implementation as much as we need to. ui/bootstrap is a great example of how to do this well because Peter & Pawel are awesome.
Isolate scopes are also great for use with transclusion. Take tabs; they are not only the whole functionality, but whatever is inside of it can be evaluated freely from within the parent scope while leaving the tabs (and panes) to do whatever they want. The tabs clearly have their own state, which belongs on the scope (to interact with the template), but that state has nothing to do with the context in which it was used - it's entirely internal to what makes a tab directive a tab directive. Further, it doesn't make much sense to use any other directives with the tabs. They're tabs - and we already got that functionality!
Surround it with more functionality or transclude more functionality, but the directive is what it is already.
All that said, I should note that there are ways around some of the limitations (i.e. features) of an isolate scope, as #ProLoser hinted at in his answer. For example, in the child scope section, I mentioned interpolation on non-directive attributes breaking when using an isolate scope (by default). But the user could, for example, simply use class="item-type-{{$parent.item.type}}" and it would once again work. So if there is a compelling reason to use an isolate scope over a child scope but you're worried about some of these limitations, know that you can work around virtually all of them if you need to.
Summary
Directives with no new scope are read-only; they're completely trusted (i.e. internal to the app) and they don't touch jack. Directives with a child scope add functionality, but they are not the only functionality. Lastly, isolate scopes are for directives that are the entire goal; they are standalone, so it's okay (and most "correct") to let them go rogue.
I wanted to get my initial thoughts out, but as I think of more things, I'll update this. But holy crap - this is long for an SO answer...
PS: Totally tangential, but since we're talking about scopes, I prefer to say "prototypical" whereas others prefer "prototypal", which seems to be more accurate but just rolls off the tongue not at all well. :-)
My personal policy and experience:
Isolated: a private sandbox
I want to create a lot of scope methods and variables that are ONLY used by my directive and are never seen or directly accessed by the user. I want to whitelist what scope data is available to me. I can use transclusion to allow the user to jump back in at the parent scope (unaffected). I do NOT want my variables and methods accessible in transcluded children.
Child: a subsection of content
I want to create scope methods and variables that CAN be accessed by the user, but are not relevant to surrounding scopes (siblings and parents) outside the context of my directive. I also would like to let ALL parent scope data to trickle down transparently.
None: simple, read-only directives
I don't really need to mess with scope methods or variables. I'm probably doing something that doesn't have to do with scopes (such as displaying simple jQuery plugins, validation, etc).
Notes
You should not let ngModel or other things directly impact your decision. You can circumvent odd behavior by doing things like ng-model=$parent.myVal (child) or ngModel: '=' (isolate).
Isolate + transclude will restore all normal behavior to sibling directives and returns to the parent scope, so don't let that affect your judgement either.
Don't mess with the scope on none because it's like putting data on scope for the bottom half of the DOM but not the top half which makes 0 sense.
Pay attention to directive priorities (don't have concrete examples of how this can affect things)
Inject services or use controllers to communicate across directives with any scope type. You can also do require: '^ngModel' to look in parent elements.
After writing a lot of directives, I've decided to use less isolated scope. Even though it is cool and you encapsulate the data and be sure not to leak data to the parent scope, it severely limits the amount of directives you can use together. So,
If the directive you're going to write is going to behave entirely on its own and you are not going to share it with other directives, go for isolated scope. (like a component you can just plug it in, with not much customization for the end developer) (it gets very trickier when you try to write sub-elements which have directives within)
If the directive you're going to write is going to just make dom manipulations which has needs no internal state of scope, or explicit scope alterations (mostly very simple things); go for no new scope. (such as ngShow, ngMouseHover, ngClick, ngRepeat)
If the directive you're going to write needs to change some elements in parent scope, but also needs to handle some internal state, go for new child scope. (such as ngController)
Be sure to check out the source code for directives: https://github.com/angular/angular.js/tree/master/src/ng/directive
It greatly helps on how to think about them
Just thought I'd add my current understanding and how it relates to other JS concepts.
Default (e.g. not declared or scope: false)
This is philosophically equivalent to using global variables. Your directive can access everything in the parent controller but it is also affecting them and being affected at the same time.
scope:{}
This is like a module, anything it wants to use needs to be passed in explicitly. If EVERY directive you use is an isolate scope it can be the equivalent of making EVERY JS file you write its own module with a lot of overhead in injecting all the dependencies.
scope: child
This is the middle ground between global variables and explicit passthrough. It's similar to javascript's prototype chain and just extends you a copy of the parent scope. If you create an isolate scope and pass in every attribute and function of the parent scope it is functionally equivalent to this.
The key is that ANY directive can be written ANY way. The different scope declarations are just there to help you organize. You can make everything a module, or you can just use all global variables and be very careful. For ease of maintenance though it's preferable to modularalize your logic into logically coherent parts.There is a balance between an open meadow and a closed jail-house. The reason this is tricky I believe is that when people learn about this they think they are learning about how directives work but actually they are learning about code/logic organization.
Another thing that helped me figure out how directives work is learning about ngInclude. ngInclude helps you include html partials. When I first started using directives I found that you could use it's template option to reduce your code but I wasn't really attaching any logic.
Of course between angular's directives and the work of the angular-ui team I haven't yet had to create my own directive that does anything substantial so my view on this may be completely wrong.
I concur with Umur. In theory, isolated scopes sound wonderful and "portable," but in building my app to involve non-trivial functionality I came across the need to incorporate several directives (some nested inside others or adding attributes to them) in order to fully write in my own HTML, which is the purpose of a Domain Specific Language.
In the end, it's too weird to have to pass every global or shared value down the chain with multiple attributes on each DOM invocation of a directive (as is required with isolate scope). It just looks dumb to repeatedly write all that in the DOM and it feels inefficient, even if these are shared objects. It also unnecessarily complicates the directive declarations. The workaround of using $parent to "reach up" and grab values from the directive HTML seems like Very Bad Form.
I, too, wound up changing my app to have mostly child scope directives with very few isolates -- only those which don't need to access ANYTHING from the parent other than what they can be passed through simple, non-repetitive attributes.
Having dreamed the dream of Domain Specific Languages for decades before there was such a thing, I'm elated that AngularJS provides this option and I know that, as more devs work in this area, we're going to see some very cool apps that are also easy for their architects to write, expand, and debug.
-- D