Angular controllers: logic as methods or add to scope? - javascript

Let's say that I had a previous function that wasn't exposed via my API ,and now I decided to expose the function (in my case for test coverage).
What are the arguments for putting objects I want accessible outside of the controller constructor as a property of $scope vs. a this.propertyName on the controller?

The only logical different is that many things have access to $scope, but not everything has access to the context of the controller. A controller is just a standard Javascript constructor function, so anything that might call it as one can access things defined on this. But HTML templates cannot: they can only see things defined on $scope. So the standard is (roughly):
Controller: Use $scope for things shared by HTML templates, directives, etc. Use this for components accessed locally or in only very limited cases such as by testing harnesses (if at all).
Directive: Use $scope for anything shared by HTML templates or other directives/controllers. Avoid using this.
Service: Use this for everything you want to expose externally. Define local (private) vars for everything else.
Filter: Avoid exposing properties/methods externally. Filters aren't meant to be used that way.

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 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.

Angular practice - using scope inheritance vs injection

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.

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