Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with: Joe Rinehart
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with: Joe Rinehart@joeRinehart )

Don't Blindly Isolate All The Scopes In AngularJS Directives

By Ben Nadel on

From what I have heard on podcasts and read in blog posts, it seems that there is a growing movement, in the AngularJS community, to make all directives isolate-scope directives. While an isolate-scope can be helpful in certain circumstances, applying the isolate-scope without reason is not advised and can actually cause problems in your AngularJS applications. Don't "default" to isolating scopes - "default" to thinking holistically about your use-case.


 
 
 

 
 Isolate all the things! 
 
 
 

Have you ever looked at the AngularJS source code to see how the core directives are implemented? If you do, you'll find something very interesting - not a single one of them (that I can see) uses the isolate scope. Sure, some of them, like ngController and ngRepeat, create new child scopes; but, none of them actually create isolate scopes.

And yet, I would argue that, as core directives, these non-isolate scope directives have to be the most reusable of any directives. After all, they have to work in every AngularJS application. So, clearly, "isolate scope" and "reusability" are not born together.

If you look at the AngularJS documentation, you'll see that they do recommend using isolate scopes in certain situations:

Best Practice: Use the scope option to create isolate scopes when making components that you want to reuse throughout your app.

To me, the key words here are, "component" and "reuse." Isolate scopes are great for creating reusable components. If you're creating a one-off component, such as a directive that helps manage the view associated with an ngController, isolate-scope probably won't make sense. Or, if you're just creating a behavior, such as binding to a click event (ex, ngClick), isolate-scope probably won't make sense. Or, if you're just creating some DOM manipulation, such as cloning templates (ex, ngRepeat), isolate-scope probably won't make sense.

But, if you're creating a reusable component, especially one that involves transclusion, then, isolate-scope may very well make sense.

If you don't agree with my philosophical interpretation of isolate-scope directives, then we can look at something more concrete. Haphazardly using isolate-scope directives will cause problems in your AngularJS applications.

If you are using AngularJS 1.0.8, then the isolate-scope will bleed into your contextual DOM (Document Object Model) tree if you're not using transclusion. This means that scope references, nested inside an isolate-scope directive, will not be able to leverage the "expected" prototypal inheritance of the scope hierarchy.

If you're using AngularJS 1.2, the next hurdle is that you cannot apply two isolate-scope directives to the same element. Take a look at the following code. All it does is apply two directives to the same element. And, the directives do nothing but require an isolate-scope:

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Multiple Isolate-Scopes Cannot Be Applied To The Same Element In AngularJS
  • </title>
  • </head>
  • <body>
  •  
  • <h1>
  • Multiple Isolate-Scopes Cannot Be Applied To The Same Element In AngularJS
  • </h1>
  •  
  • <!-- Both of these directives are isolate-scope directives. -->
  • <p bn-this bn-that>
  • Look at the console output.
  • </p>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/jquery/jquery-2.1.0.min.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.2.26.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I request an isolate scope directive.
  • app.directive(
  • "bnThis",
  • function() {
  •  
  • // Return the directive configuration. Notice that we are creating an
  • // isolate scope, even though we are not binding any expressions.
  • return({
  • link: angular.noop,
  • restrict: "A",
  • scope: {}
  • });
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I request an isolate scope directive.
  • app.directive(
  • "bnThat",
  • function() {
  •  
  • // Return the directive configuration. Notice that we are creating an
  • // isolate scope, even though we are not binding any expressions.
  • return({
  • link: angular.noop,
  • restrict: "A",
  • scope: {}
  • });
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

When we run this, AngularJS fails to compile, with the following error:

Error: error:multidir
Multiple Directive Resource Contention
Multiple directives [bnThat, bnThis] asking for new/isolated scope on

Now, imagine that the AngularJS core directives all used isolate-scope. Think about how often do you use combinations like ngRepeat and ngClass on the same element; or, ngStyle and ngClick on the same element. If AngularJS "defaulted" to using the isolate-scope for its directives, they would become completely unusable.

In the end, I'm not arguing against isolate-scope directives. I think they can be really useful when creating reusable components. I'm just saying that you should never blindly apply the isolate-scope. Doing so will cause unexpected behaviors and even errors in your AngularJS applications.




Reader Comments

Great tip. As I learn more and more about the inner parts of angular (and glad you mentioned looking at core directives, you learn a lot from reading the core files) these points start to come into play when creating directives. Originally, I would blindly isolate my directive scopes. However now, especially with "decorative" directives that supply additional interactive functionality, rather than as a component, I see why taking advantage of attributes is better than initiating a new isolated scope. And, depending on the scenario, you can watch these for changes.

Question for you -- if I do not define an isolate scope, but I use the scope passed into the post link function, this scope is the scope from the context that the directive was loaded into, right? If the directive was within a view scoped to ControllerA, then ControllerA's scope is being used by a directive that doesn't isolate its own scope?

Reply to this Comment

Follow up --

When an isolated scope is created, is this just doing scope.$new() on the existing (now the new parent) scope?

Reply to this Comment

@Atticus,

To your first question, Yes. If the directive is not using an isolate scope, then its link() scope is the same as the one being used by the parent Controller. Unless, something in between the directive and the Controller also creates a new, non-isolate scope (ex. ngRepeat) or creates an isolate scope.

As far as how the scope is implemented, I *believe* you are correct. If you look at the Scope.$new() method signature, it is:

Scope.$new( [ isolate [, parent ] ] );

So, I assume AngularJS is just doing "scope.$new( true )" behind the scenes. But, without looking at the source code, I'm not 100% sure.

Reply to this Comment

I think an easy way for me to think about it is if you're making an Element directive then you may need to isolate, If you're making an Attribute directive then probably not.

Reply to this Comment

@Jonathan,

I think that works pretty well as a decision model. An element directive is much more likely to have transcluded content, which feels like the primary requirement of isolation.

Reply to this Comment

Cool post! I'm currently running into this issue with a couple directives on my project. I've been using isolated scopes more less as a way to pass information/objects to my directives. In the past, I used the $parse service, or just used `attrs`.

Would you be able to maybe post an example of converting a simple isolated scope directive into a non-isolated scope directive?

Thanks!

Reply to this Comment

Ben, you are spot on. I just hit the "Multiple Directive Resource Contention" error, and started wondering exactly the same thing about using many built in Angular directives on the same element.

And yet, when I search for ways to read and update variables passed in from directives without their own scopes, most of the SO answers end up saying "isolate your scope".

I'm like "no! I want to do it the scope: false way!" Now $parse is my friend.

Reply to this Comment

I wish I would have read this suggestion earlier. I also ran into the same problem some days ago. I am relatively new to angular and I also created some directives and I was blindly using the isolated scope options just because I found it on all the examples, I found on the internet. After struggling some hours and wondering, why the heck I could not access the properties I needed, I just recognized, that a parent directive created an isolated scope and so it hid all the stuff I needed in my child directive.

I just read some news about angular 2 and if I got it right, it seems like it will only make use of isolated scopes. I am looking forward to see how they will fix the multiple directive problem.

Not using some patterns when you know it better and made your experiences is a good think. Or at least thinking about when they are appropriate and when not. But as a newbie you often work with the examples you read somewhere else and then it gets tough.

But hey, after all I learned an important lesson by making that mistakes and that is, what counts.

Reply to this Comment

isolated scope + child scope => Won't work! Only one scope can be related to one element. Therefore these directives cannot be applied to the same element.
isolated scope + isolated scope => Won't work! Only one scope can be related to one element. Therefore these directives cannot be applied to the same element.

this is from angular docs

Reply to this Comment

@Ben, I really like your posts.

But the code sections are terrible.

Too many lines , owful colors - not clear.

Have you thought about something else / like stacoverflow code colors/fonts?

Am I the only one who said that to you?

Don't get me wrong , I really love your articles and thank you for that but every time I come to the code section , it seems like a very old site section.

Also it would be great to tokenize code words like :

http://i.imgur.com/SK7KYRw.png

Thank you.

Reply to this Comment

@Royi,

The default color scheme is just gray. But then, the code samples are supposed to be replaced with GitHub-based Gist files (which are color coded using GitHub's color coding libraries). If you're seeing the flat gray coloring then the Gist isn't loading for some reason :(

For example, this blog post is supposed to load this gist on DOM-ready:

https://gist.github.com/bennadel/60b7d8448324792337e6#file-multi-isolate-htm

Not sure why it wouldn't be loading for you.

Reply to this Comment

@All,

The more I learn about AngularJS, and the more my viewpoint evolves, the more clearly I see that there are distinct types of directives. All of the internal AngularJS directives - and many of my directives - are "behavioral" directives. Meaning, they provide some behavior hook, like Click or Mouseenter, and pipe that into a directive. These are often attribute-oriented directives and should never use the isolate scope.

Then, there's DOM-manipulation directives, like ngShow, ngHide, and ngRepeat. These should basically never use isolate scope either as they deal completely with expressions that are passed into the directive.

And then, finally, there's the "component directive", which is meant to be a "reusable widget" within an application. These are likely the only kind of directives that actually benefit from isolation. And, even then, I would argue that isolation is only needed when you also have transclusion.

Now, one could argue that always turning the isolate scope on for a "component directive" is good because it protects the developer from him/herself making silly mistakes like accidentally using an inherited value. But, by that argument, you could say that we should stop using JavaScript because it lacks type-checking.

At the end of the day, the moral of this story, including my evolved understanding is that you shouldn't turn on the isolate scope unless you understand what it does and why you need it.

Reply to this Comment

Thank you for this great article. Really useful and informative about scopes.
Though, I have a question that I can't find an answer to:
When should I use "inherited scope" (i.e scope:true) and when should I use "parent scope" (i.e scope:false)?

For example, why some of the core angular directives (such as ng-if and ng-repeat) use scope:true ,while other directives do not?

I know the difference between the scope types, but I am just not sure when I should decide to use one of them rather than the other.

Would appreciate your opinion on this.
Thanks in advance :)

Reply to this Comment

Almost on spot, but not quite.

However, any reader can easily conclude when it is better to use isolate scopes from your examples: ngStyle, ngClick, ngClass etc. They don't add new stuff to DOM (templates) but rather they modify attributes/events of the element they are applied to. That is exactly when you should not use isolate scopes.

If your directive has a template or transclusion is set, then you must use isolate scopes in most cases EVEN IF you don't plan to reuse the component. As simple as that.

Reply to this Comment

@Anil,

Just an update after reading Ben's last comment. It's very unlikely for a developer to create something like ngRepeat. When one does, he will know what to do (at which point he should be master of scopes, private scope variables, transclusions, compiling, linking etc). So my point is still valid for >90% of cases.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.