Angular practice - using scope inheritance vs injection - javascript

I have a question that came up in a meeting that I would like to get some expert opinion on.
lets say I have a 'userService' that many controllers in my app need. (in fact most of them).
It was my belief that any parent controllers would inject this userService and expose properties of that service to child controllers. Since any 'child' controllers have access to parent scope, there is no need to inject the 'userService' into every controller.
Another person's opinion was to inject the 'userService' into every controller that needs it, regardless if the service is already inherited from it's parent, basically because it makes the code easier to read and easy to find where your variables are coming from.
To me, this view seems wrong, and misses the point of Angular and how it uses prototype inheritance, and the power of scope. Doesn't this actually create unnecessary instances in memory that reference the same thing ? why would you want to do that ?
I would love to hear some experience opinions on this.
Thanks!

The references in memory are cheap. Don't worry about those. More importantly, you're implicitly creating a dependency that leads to a non-reusable component when you put something on scope that a child needs to do its' job. You should instead declare your dependencies upfront--via the argument list. You'll thank yourself later when you come to testing. Scope inheritance is for templates--not controllers.

There's some arguments both ways, but I avoid scope inheritance whenever possible. I even isolate scopes on most of my directives. The reason is that you can more naturally de-couple and encapsulate your code. Encapsulation is less of a problem on controllers in general, but it's nice to know that wherever you are in a scope hierarchy you can always get to your service.

Someone can correct me if I'm wrong, but I think it works like this.
Controller A injects service A. It can access it via A.method
Controller B inherits from Controller A but cannot access service A as it doesn't know what it is.
Scope inheritance comes into play when you assign that service to a scope variable. If we change it now so that Controller A sets $scope.A = A THEN Controller B will be able to access the service via $scope.A.method as it has inherited this.
I don't think the injected services themselves are inherited.
In terms of memory, I wouldn't worry too much. Angular won't be creating a new instance, just a reference to it. Services are singletons and won't get recreated unless they're destroyed. If you're using Factories, then you may get new objects but that's different.

Related

$scope and $rootscope variables' life term

In angularjs, I see a lot of usages for $scope and $rootscope.
I like to understand the life term of variables attached to $scope and $rootscope.
Say, I have a.js/a.html and e.js/e.html.
a.js has $scope attached with variables say $scope.b, $scope.c, $scope.d for use in a.html.
If I load another page with e.js and e.html. Those $scope.b, $scope.c, $scope.d still have lives or they are removed. If I have $scope.b, $scope.c, $scope.d again in e.js, what could be issue?
I just want to highlight the life term of those attached variables.
$rootScope - Usually configured in your main app.js file where you define your angular main module and main configuration. Even if not configured, it can always be injected.
$scope - your current scope in the current controller (page).
$rootScope variables live as long as you dont refresh your page. That means that they exist trough all transitions of your SPA.
$scope variables live only on the current scope (again, as long as you dont refresh the page) and they will get disposed once you transition to a different controller/page.
$rootScope lives as long as the whole page does
$scope instances are destroyed whenever the controller or directive using it are no longer active
No javascript is persistent between full page loads in browser
$rootScope is global while $scope is local. When Controller is assigned to a page, so a $scope variable can be use here because it binds to this controller. But when we want to share its value across to other controllers or services, then $rootScope is being used (**there are alternative ways share values across but in this case we want to use $rootScope).
AngularJS Scope Life-Cycle
A property assigned with $scope cannot be used outside the controller in which it is defined whereas a property assigned with $rootScope can be used anywhere.
Which means when you want variable witch can be use in single variable to use in multiple controller (whole application), you can use $rootscope
$rootScope exists, but it can be used for evil
Scopes in Angular
form a hierarchy, prototypally inheriting from a root scope at the top
of the tree. Usually this can be ignored, since most views have a
controller, and therefore a scope, of their own.
Occasionally
there are pieces of data that you want to make global to the whole
app. For these, you can inject $rootScope and set values on it like
any other scope. Since the scopes inherit from the root scope, these
values will be available to the expressions attached to directives
like ng-show just like values on your local $scope.
Of course,
global state sucks and you should use $rootScope sparingly, like you
would (hopefully) use with global variables in any language. In
particular, don't use it for code, only data. If you're tempted to put
a function on $rootScope, it's almost always better to put it in a
service that can be injected where it's needed, and more easily
tested.
Conversely, don't create a service whose only purpose in
life is to store and return bits of data.
— AngularJS Docs Miscellaneous - FAQ

Why using $rootScope with functions is not recommended?

While am looking into the FEQs of Angularjs I've seen below article:
$rootScope exists, but it can be used for evil
Scopes in Angular form a hierarchy, prototypally inheriting from a root scope at the top of the tree. Usually this can be ignored, since most views have a controller, and therefore a scope, of their own.
Occasionally there are pieces of data that you want to make global to the whole app. For these, you can inject $rootScope and set values on it like any other scope. Since the scopes inherit from the root scope, these values will be available to the expressions attached to directives like ng-show just like values on your local $scope.
Of course, global state sucks and you should use $rootScope sparingly, like you would (hopefully) use with global variables in any language. In particular, don't use it for code, only data. If you're tempted to put a function on $rootScope, it's almost always better to put it in a service that can be injected where it's needed, and more easily tested.
Conversely, don't create a service whose only purpose in life is to store and return bits of data.
— AngularJS FAQ - $rootScope exists, but it can be used for evil
So My doubt is why $rootScope is not recommended for functions as a global function? Is there any performance issue?
I've answered this in the past, but it's good that you're asking these questions.
$rootScope exists, but it can be used for evil Scopes in Angular form a hierarchy, prototypally inheriting from a root scope at the top of the tree. Usually this can be ignored, since most views have a controller, and therefore a scope, of their own.
Non-isolated scopes are hierarchical, but most developers should be using directives that have isolated scopes. The very hierarchical nature of AngularJS's scope is the source of many bugs in angular apps. It's a problem I like to call scope bleeding where a scope property is modified magically somewhere in the DOM tree and you don't know why.
Angular's default behavior is to inherent scopes and this makes it tempting for one controller to update something managed by another controller, so on, and so on. This is how spaghetti connections between source code is created. Making it very difficult to maintain that code.
Occasionally there are pieces of data that you want to make global to the whole app. For these, you can inject $rootScope and set values on it like any other scope.
No that's not correct. AngularJS allows you to define things like constants, values, and services. These are things that can be injected into routes, controllers and directives. That is how you make things accessible globally to your app, and this how you do it if you want to make your controllers or directives testable. A unit test writer doesn't know what properties should be in the $rootScope that a directive or controller depends upon. They have to assume that the $rootScope has not mutated to provide a service or data.
Of course, global state sucks and you should use $rootScope sparingly, like you would (hopefully) use with global variables in any language.
The problem isn't $rootScope but what people are doing with it. Many apps add the current user, the auth tokens, and the session data into the rootScope. This ends up getting used heavily in templates (shows X if user logged in otherwise show Y). The problem is that the HTML doesn't communicate scope hierarchy. So when you see {{user.firstname + ' ' + user.lastname}} you have no idea where the variable user came from. The second problem is child scopes can shadow root properties. As in the previous example if a directive does this scope.user = 'bla bla bla'. It hasn't replaced the value on the rootScope. It's hidden it. Now you get some strange unexpected things in the templates, and you don't know why the variable user has changed.
Conversely, don't create a service whose only purpose in life is to store and return bits of data.
Angular's $cacheFactory and $templateCache are examples of services that exist only too store data. I think the author was trying to encourage the use of constants and values in Angular's modules, but that's not a good description to do that.
So My doubt is why $rootScope is not recommended for functions as a global function? Is there any performance issue?
The $rootScope is the only scope available during angular.config(..). It's during this time that the scope can be modified if this is the only time that you can do it. For example; you may need to inject an API key or Google anayltics variable before the app starts.
Functions on any scope are generally a bad idea. Mainly for the reason that everything in scopes is digested in expressions on the templates. Functions tent to hide heavy operations. It's impossible to tell how heavy a template is by reading the HTML when it calls a function. I've seen scope functions like getHeight() where the function itself performed 3 levels of nested loops. That function has to get called every time angular digests the watchers to see if it's changed. You should try to keep your templates as dry as possible.
Global Variables are Abused
$rootScope is pretty much a global variable and has its place but is definitely abused by most of the people that use it. These are the reasons Globals in general should not be used.
Non-locality -- Source code is easiest to understand when the scope of its individual elements are limited. Global variables can be read or modified by any part of the program, making it difficult to remember or reason about every possible use.
No Access Control or Constraint Checking -- A global variable can be get or set by any part of the program, and any rules regarding its use can be easily broken or forgotten. (In other words, get/set accessors are generally preferable over direct data access, and this is even more so for global data.) By extension, the lack of access control greatly hinders achieving security in situations where you may wish to run untrusted code (such as working with 3rd party plugins).
Implicit coupling -- A program with many global variables often has tight couplings between some of those variables, and couplings between variables and functions. Grouping coupled items into cohesive units usually leads to better programs.
Concurrency issues -- if globals can be accessed by multiple threads of execution, synchronization is necessary (and too-often neglected). When dynamically linking modules with globals, the composed system might not be thread-safe even if the two independent modules tested in dozens of different contexts were safe.
Namespace pollution -- Global names are available everywhere. You may unknowingly end up using a global when you think you are using a local (by misspelling or forgetting to declare the local) or vice versa. Also, if you ever have to link together modules that have the same global variable names, if you are lucky, you will get linking errors. If you are unlucky, the linker will simply treat all uses of the same name as the same object.
Memory allocation issues -- Some environments have memory allocation schemes that make allocation of globals tricky. This is especially true in languages where "constructors" have side-effects other than allocation (because, in that case, you can express unsafe situations where two globals mutually depend on one another). Also, when dynamically linking modules, it can be unclear whether different libraries have their own instances of globals or whether the globals are shared.
Testing and Confinement - source that utilizes globals is somewhat more difficult to test because one cannot readily set up a 'clean' environment between runs. More generally, source that utilizes global services of any sort (e.g. reading and writing files or databases) that aren't explicitly provided to that source is difficult to test for the same reason. For communicating systems, the ability to test system invariants may require running more than one 'copy' of a system simultaneously, which is greatly hindered by any use of shared services - including global memory - that are not provided for sharing as part of the test.
Source: http://c2.com/cgi/wiki?GlobalVariablesAreBad
Sharing data in Angular
When it comes to sharing data across controllers in Angular you should use a service. With your custom service you can create a getter and a setter method. You inject it to the controllers you need it and can use it in your app.
There exist no performance issues. It would actually boost your performance by a fraction of time, because you dont need to dependency-inject a lot of services.
But it's a big concern of design. Consider a large application with dozens and dozens of views, complex components and tied to a number of well known APIs (e.g. Twitter, Flickr, Facebook, OAuth, ...).
You wont develop this application alone. The following issues will arise:
Namespacing
You are working on the Facebook API, someone else is working on the Twitter API. You both think that using $rootScope for functions is a good idea and both write a $rootScope.login function. How would you resolve this when doing git merge? You need namespacing, alas, you need to develop two services myFacebookAPI, myTwitterAPI which then can implement the same interface for loggin in. (login(user,pw)). Note that this gives you the ability to abstract away the actual social network you are dealing with in the controller, when you can do something like:
$scope.callAction = function (action) {
var service;
if ($scope.serviceSelected === 'fb') {
service = myFacebookAPI;
} else {
service = myTwitterAPI;
}
service[action]();
};
Testing
When developing professionally, you write tests. Angular gives you tools to do automated tests for services, etc., but you wont be able to test something you assign to $rootScope in the same comfortable manner.
Other issues will arise too, but i think this should be enough for you to think about it on your own.

Why would you ever use the $rootScope?

Ok so I was reading here
basically when I have this
MyApp = MyApp || {};
MyApp.settings = {
isFooEnabled: false
}
if I use the rootscope and want to check if isFooEnabled I have to inject the rootScope into whatever object I want to do the check.
How does that make sense?
What is the advantage of using $rootScope.isFooEnabled over using straight standard javascript MyApp.isFooEnabled?
what is better for what?
when should I use one over the other?
The $rootScope is the top-most scope. An app can have only one $rootScope which will be shared among all the components of an app. Hence it acts like a global variable. All other $scopes are children of the $rootScope.
The rootScope's variable is set when the module initializes, and then each of the inherited scope's get their own copy which can be set independently.
NOTE:
When you use ng-model with $rootScope objects then AngularJS updates those objects under a specific $scope of a controller but not
at global level $rootScope.
The $rootScope shouldn't be used to share variables when we have things like services and factories.
Finally, Angular FAQ says this at the bottom of the page: "Conversely, don't create a service whose only purpose in life is to store and return bits of data." See from here.
Actually, I would argue that you shouldn't use $rootScope in this case, you should create a separate service (or factory) that stores your settings, however, the usage and reasons are the same.
For simply storing values, the primary reason is consistency. Modules and dependency injection are a big part of angular to ensure you write testable code, and these all use dependency injection so that unit tests can be written easily (dependencies can be mocked). Whilst there are not many obvious gains from injecting a simple object, it's consistent with the way more complex code is accessed, and there is a lot to be said for that. On a similar note, if you were to upgrade your settings object to fetch data from the server (e.g. for environment specific settings), you might want to start unit testing that functionality, which you can't really do properly without modularising it.
There is also the (frankly weak) namespacing argument - what if another library you import uses window.MyApp?
TL;DR: It's a strongly recommended best-practice. It might seem a bit clunky now, but you'll benefit from doing it in the long run.

Angularjs calling function in another file

My question is a lot like this: AngularJS seed: putting JavaScript into separate files (app.js, controllers.js, directives.js, filters.js, services.js) Just can I make a function in one of the files that can be called by another? And if so what do I have to add? The only reason I'm not adding to it is because it is closed. Any help would be great!
Accessing another controller would be completely wrong. You have a lot of ways to communicate between them. Here are 3 easy ones:
Scope: you can access the scope of all your parent controller using $parent or simply the $rootScope (don't overuse it or your root scope will be clutter). Remember that your scope always have your parent scope objects too until you change them. Using $watch on a scope variable can make you code cleaner when needed. (see the scope documentation)
Event: Pretty straightforward. You listen with $scope.$on and you send it with $scope.$broadcast or $scope.$emit (depending if you want the notice the children or the parents).
Service: if what you are trying to implement has no clear controller owner, you might have to consider using a service. Then, you can use it within all your controllers. (See Creating Services)
Most of the time, you will end up using the scope because you want to interact with your parent controller. Let me know if anything is unclear.

AngularJS GlobalCtrl vs $rootScope vs Service

I am confused on a couple of things with Globals in Angular. Below is my pseudo code.
1) With the way I have my GlobalCtrl placed, I am able to reference my $scope.modalOptions from all of my controllers. That being the case, I'm confused as to why I see people adding global properties to $rootScope instead of just adding them like I am doing here. Is that just in case they want to inject it into a service or something?
2) Should I be using a service instead of adding properties and methods to my GlobalCtrl? If so, why?
<div ng-app="app" ng-controller="GlobalCtrl">
<div ng-view></div>
</div>
function GlobalCtrl($scope, $location) {
$scope.modalOptions = {
backdropFade: true,
dialogFade: true
};
}
The 'Main Controller' approach is definitely preferable to using $rootScope.
Scope inheritance is there, so why not leverage it. In my opinion, that solution works well for most cases, i.e. unless you need to have a parallel controller somewhere (that wouldn't be a child of Main). In that case, the best way to go is to use a service and inject it where needed. Services (or rather factories, because that's what you'll probably be using -- read more about them here) are singletons and work well for sharing data across controllers.
Important to know about scopes
Scope inheritance is pretty much regular JavaScript inheritance at play. You should tend to use objects for your data, because they are passed by reference.
If you have a primitive like $scope.myString = 'is of a primitive data type'; in your parent controller and try to overwrite the value in a child controller, the result won't be what you'd expect -- it will create a new string on the child controller instead of writing to the parent.
Suggested reading here
Final thoughts
If you are using the nested controllers approach, do not forget to still inject $scope (and other dependencies) in the child controller. It might work without, but it's slower and hard to test, and last but not least -- the wrong way to do it.
Finally, if you have a lot of state variables to keep track of and/or a lot of usage points, it's definitely a good idea to extract them into a service.
Generally speaking global variables are considered bad practice, as they don't encourage encapsulation, make debugging difficult, and promote bloated code. Here's a good discussion of global variables: http://c2.com/cgi/wiki?GlobalVariablesAreBad.
A good rule of thumb is to add properties and methods to the most local scope possible and use services to share data between modules.

Categories

Resources