I have some server side enums that I'm sending down to an angular application.
Ideally, I'd like to be able to access the enums for this sort of behavior:
<select ng-options="type.name as type.value for type in Enums.TYPES" />
I've tried several things to get this working:
angular.module('myModule').constant('Enums', {myEnumObject})
var Enums = {myEnumObject};
$window.Enums = {myEnumObject};
obviously, none of these ways make the object accessible from the view. I've also tried using services to return the object, but that doesn't make it accessible from the view.
My problem here is that I know it can be done from the scopes, using one of these:
$rootScope.Enums = {myEnumObject};
OR
$scope.Enums = {myEnumObject};
My concern with this is that this seems unsustainable. Using a scope seems like bad practice since every child scope created will be polluted with this data.
I could also do it by assigning the enums to a controller, but then that seems like it's kind of defeating the purpose of having these global objects. In reality, they ARE constants that never change.
It seems like I must be missing something here. Can somebody point me in the right direction for maintaining sustainability for this code, as well as handling it in an "angular" way. Thanks.
One way to avoid attaching the constant or service enum to every scope you need, is to take a directive centric design to your application. As directives can have isolate scopes, you can bundle your views/controller and scope bindings together in a nice reusable package. For example:
.directive('SomeEnumThing', function(Enums){
return {
scope: {}, // don't forget to set an isolate scope on the directive
templateUrl: 'sometpl.html',
link: function(scope, elem, attrs){
// bind the enums to your directive's scope
scope.enums = Enums;
}
};
});
The only downsides to this method are the extra verbosity in writing a directive (but ultimately more reusable), and the added requirement of setting up any other necessary bindings with outside objects (as you are now outside the general scope hierarchy).
You could do:
angular.module('myModule').constant('Enums', {myEnumObject})
Then in any scope that you want to use it, you can
$scope.Enums = Enums;
This is not very polluting, only a scope that requires it will have it set.
Global constants can be put on the $rootScope, and this can be seen as polluting. But if this is something that you do need all throughout your app in various directives, it's not such a big deal (IMO). There's no correct answer to this question.
Related
I would like to persuade my co-worker that it is a better approach to use component directives than to use $rootScope everywhere. I need arguments against his ones because he is very stubborn and a very good speaker (which I am not). He thinks that $rootScope prevents spaghetti code. This week I have refactored the project and there are no more spaghetti but I don't want him to rework everything to $rootScope.
Please tell me about problems and issues that can arise when using $rootScope. Thank you.
EDIT
Are there any security issues with $rootScope?
EDIT 2
My friend came with this construct and wants to put it in every component:
function Controller(service, $rootScope, $scope) {
var vm = this;
$scope.a = $rootScope.a;
$scope.b = $rootScope.b;
$scope.c = $rootScope.c;
$rootScope.$watch('mapLoaded', function () {
$scope.a = $rootScope.a;
$scope.b = $rootScope.b;
$scope.c = $rootScope.c;
}, true);
Would the issue of destroying scopes and removing wathces that #charlietfl described in comments appear? I am definitelly not gonna let him code like this but I need the arguments against it. Thanks again.
$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
I will response myself to Edit 2 citing this:
Using $watch means whenever you read this code in the future you’ll
have to consider whether it’s being triggered by something else, too.
This is an angularjs app. I have a service that handles the loading of content (ajax). While the service is getting the content, a number of things throughout the app hide, later showing again (depending on the content returned). They might have the same scope, different scope, whatever. They just need to hide while content is loading, and then show when it's done. Pretty normal stuff.
Right now, I have separate controllers watching a "loading" property of the service and using regular angular directives (ng-show, ng-hide, etc.) to show/hide. But this feels like overkill. I'd prefer to write a custom "loading" directive that injects the loading service and does the watching and showing/hiding.
My question is: Is what I want to do "bad"? The controller way, I end up boilerplating a bunch of code, maybe up to like 5 or 6 times, or even more as the app grows. The custom directive way, I write it once and use an attribute where I need it. Yeah - there's a dependency on that service, but that just doesn't feel like the end of the world that some people have made me start to think I should think it is.
For what it's worth, I feel like I've heard "separation of concerns" so many times I've become paralyzed by it. It leads me to overthink everything because I want to do things the right way, but it sure doesn't feel like I'm being very productive.
If I understood correctly, you have a bunch elements that should hidden when a particular service is loading data, and then be displayed again when the data is loaded, right?
In that case, events might be a good solution:
they can be global to the appliciation (which i think is what you are aksing for).
they allow for avoiding direct coupling between elements (also one of your concerns).
So, in your service, just broadcast events when stuff happens:
$rootScope.$broadcast('loading-data');
axajStuffOrWhatever(function() {
$rootScope.$broadcast('data-loaded');
});
Then, wrap the show/hide behaviour in a directive that will listen to those events.
.directive('hideWhileLoadingData', function() {
return {
link: function(scope, el, attrs) {
scope.$on('loading-data', function() {
el.css('display', 'none');
});
scope.$on('data-ready', function() {
el.css('display', 'block');
});
}
};
});
Use the directive:
<div hide-while-loading-data>something</div>
The advantage of using events here, is that later on, they could be originated by a different service, or by multiple services, and the directive will not be affected by that as long as the events are the same.
For more complex behaviour, you could also parametrize the events and the directive, so different elements will react to different kind of stuff.
I've made an example of this.
In my opinion all scopes which depend on this service should be children of one parent scope. If you have the parent scope responsible for talking with the service then any directive of any scope can access it via $parent on the $scope.
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
Normally in all examples/source code of AngularJS modifications of scope is done in controllers. In my directive I need to get some information from another directive (or it's scope) and put it into scope (so visible in template of directive). As this information is common for all instances of this directive, using scope binding does not sound good for me.
So the only solution I found is to modify instance scope in linking function:
link: function(scope, element, attr, parentCtrl) {
scope.data = parentCtrl.someData;
}
This solution works. Plnkr example
The question: Is it ok according to AngularJS philosophy/style to modify scope in linking function or there is another solution?
Since you are creating isolate scopes in your directives (in your example plnkr), and you want to allow for parents to be 'somewhere' in the scope hierarchy (according to your comment to #MathewBerg), I believe your only option is to use the linking function to modify the scope.
(I suppose you could define methods on your MainCtrl that only the child directives should call, but enforcing that would be messy and break encapsulation).
So, to echo what #MathewBerg already said, yes, modify the scope in the directive/linking function.
Modifying scope in directives is fine. As for sharing information between directives there are a few methods. One is the way you described, where you access the parents controller and get it's data, another very similar method would be to have
scope.data = scope.$parent.data;
Instead of
scope.data = parentCtrl.someData;
The general way to share stuff between directives though is to use a service. This would allow you to inject the service into each directive and they can share the values. The problem with your initial method (and the one that I've described) is that if you ever move the element around so that the hierarchy of scopes change, your code will break. This is why I would recommend using a service over both. I suggest reading up on the service docs. There's also plenty of videos out there describing how to set them up: http://www.youtube.com/watch?v=1OALSkJGsRw
To get a grip on AngularJS I decided to play around with one of the examples, specifically, simply adding a "complete" screen to the Todo-example, when the user has entered 5 todos it uses a switch-case to switch to another div. Code is available here http://jsfiddle.net/FWCHU/1/ if it's of any use.
However, it appears that each switch-case gets its own scope ($scope.todoText is not available), however it can be accessed using "this" from within addTodo() in this case. So far so good, but say I want to access todoText (which is inside the switch-case) outside of the switch-case, how would I go about doing that? Can I bind the switch-case scope to the model perhaps, is it accessible in some other way or should this be solved in some other way?
PS. I'm not trying to find ANY solution to the code above, I'm pretty sure I know how to solve it without using switch-cases, I want to understand how scopes work in this case!
Mark has some great suggestions! Make sure you also check out the AngularJS Batarang Chrome Extension to see the various scopes and their values (among other things). Note it doesn't appear to work well with jsFiddle.
I'm not sure how to access inner scopes directly but here is one way to access the same text in the outer scope by binding to an object instead of a primitive.
1) Declare todoText as an object instead of a primitive in your controller:
$scope.todoText = {text: ''};
2) Bind to todoText.text instead of just todoText:
<form ng-submit="addTodo()">
<input type="text" ng-model="todoText.text" size="30" placeholder="add new todo here">
<input class="btn-primary" type="submit" value="add">
</form>
3) Modify the existing functions to use todoText.text:
$scope.addTodo = function() {
$scope.todos.push({text:$scope.todoText.text, done:false, width: Math.floor(Math.random() * 100) + 50});
$scope.todoText.text = '';
};
Take a look at this fiddle and note that the text displayed beneath the textbox when you type something in is accessing the todoText.text on the outside scope.
If you change the code back to use a primitive (like in this fiddle) the parent scope todoText won't reflect any changes you make to the textbox. This is likely more to do with how JavaScript copies reference values (see this post for more info) and less an AngularJS specific thing.
Update2: Now that I know a little more about AngularJS, here's a much better answer.
say I want to access todoText (which is inside the switch-case)
outside of the switch-case, how would I go about doing that?
There is no way for parent scopes to access child scopes. (One reason for this restriction, according to Angular developers, is for easier memory management of scopes.) (Well, you could use $$childHead and $$childTail to access child scope, but you shouldn't!)
Can I bind the switch-case scope to the model perhaps, is it
accessible in some other way or should this be solved in some other
way?
There are three common ways to access the parent model from the child scope:
Do what #Gloopy suggests: create an object in the parent scope, then refer to properties on that object in the child scope.
Use $parent in the child scope to access the parent scope and its properties, even primitive properties.
Call a method on the parent scope
To convert your fiddle to use $parent:
<input type="text" ng-model="$parent.todoText" ...
$scope.addTodo = function() {
$scope.todos.push({text: $scope.todoText, ...
$scope.todoText = '';
As I mentioned in the comments on Gloopy's answer, ng-repeat and ng-switch both have the new child scope prototypically inherit from the parent scope. ng-repeat also copies the loop variable/item to the new child scope (and the nuances that #Gloopy describes with primitives vs object applies). ng-switch does not copy anything from the parent scope.
To see what the inner/child scope looks like, add the following after the ng-switch-when:
<a ng-click="showScope($event)">show scope</a>
and add this to your controller:
$scope.showScope = function(e) {
console.log(angular.element(e.srcElement).scope());
}
Update1: (strikethroughs added to bad advice, []'s added for clarity)
For this scenario, where AngularJS is creating additional inner scopes (implicitly), and you don't really want/need another controller, I like Gloopy's solution. A service (what I originally suggested below) is [the wrong way to do this] probably overkill here. I also like that Gloopy's solution does not require the use of 'this' in the controller methods.
Original Answer: (strikethroughs added to bad advice, []'s added for clarity)
To see where scopes are being created (if you haven't tried this already, it is handy):
.ng-scope { margin: 4px; border: 1px dashed red }
To access todoText outside the switch-case (hence outside of its scope), you're essentially asking about inter-controller communication, since multiple scopes are involved. You have a few options, but a service is probably best. Store the data (that needs to be shared) inside the service, and inject that service into each controller that needs access to the data.
For your specific example, I think you'd need to attach a controller to each switch-case and inject the service into it, to get access to the shared data.
See also AngularJS: How can I pass variables between controllers?.
The other options:
Using $scope.$parent in the inner scope is [one way to do this -- see Update2 above] not recommended, since then a controller would be making assumptions about how the data is presented.
Using $rootScope is not recommended, except maybe for simple, one-off applications. That shared data may start to take on a life of its own, and $rootScope is not the place for that to happen. Services are easier to reuse, to add behavior to, etc.
Using $scope.$emit is another option, but it seems messy and a bit odd: emitting events to share data (instead of triggering behavior).
[Using an object in the parent scope is probably best -- see #Gloopy's answer.]