The other day, in my AngularJS "code smell" blog post about directives, I stated that the content may not apply to directives that use the "isolate" scope. The truth is, I don't know too much about the isolate scope feature since I never really understood the use-case. But, I'm tired of not knowing; so, I'm going to start digging into it. This blog post is mostly for me (I think better when I write) - my first AngularJS directive that uses the isolate scope.
The point of the isolate scope, from what I have read, is to maintain a bubble around the directive that prevents it from accidentally reading from or writing to the parent $scope chain. Communication through this bubble wall can only be done through explicitly defined openings in the isolate scope configuration. This configuration maps directive-local scope properties to parent-scope expression evaluation or bi-directional parent-scope property bindings; both, of which, are defined using element attributes on the DOM (Document Object Modal) tree.
The isolate scope configuration can also map Element attribute values onto directive scope bindings. But, as of this writing, I am not sure how (if at all) this differs from the Attributes collection, which is still available inside of an isolate scope directive. It seems the only difference would be that one is monitored using scope.$watch() and the other is monitored using attributes.$observe(). But, that's a blog post for another day.
To experiment with this idea, I've created an isolate scope directive which tracks mouse-down events on the document. The goal here is to evaluate an expression on the parent scope (using isolate scope bindings) when the user mouses-down outside of the directive element:
But, in order to make this a more meaty exploration, I also want to make use of the property and attribute bindings as well. As such, you can also enabled and disable the directive using a scope property and tell it to ignore mouse-down events inside elements based on CSS selectors:
ignore-mousedown-if="[ flag for enabled/disabled ]"
ignore-mousedown-inside="[ css selectors to ignore ]"
Ok, let's look at some code:
In this case, because I wrote both the demo and the directive, I know that all of the various scope properties and DOM tree attributes will exist. As a directive author, however, you don't. As such, your isolate scope mappings won't always correspond to actual values. In such cases, the directive scope properties will show up as "undefined."
NOTE: The mapped property keys still exist in the scope; but, the value of the property is "undefined."
You may notice that when I invoke the callback, inside of the isolate scope, I am using the normal $apply() method to tell AngularJS about potential model changes. Even though this scope is outside of the normal scope chain, the $apply() method still works in the greater context of the application. This is because the $apply() method triggers a $digest on the $rootScope, regardless of where the $apply() call was initiated.
As this is my first isolate scope directive, I find it all very interesting. But, I am not sure that I fully realize the benefits at this time. I do like that the scope bindings force you to explicitly define all of your communication channels. But the same could be done with "best practice" conventions. There's still a lot more to explore, however, so I will try to defer any judgement until I have a more well-rounded understanding.
Want to use code from this post? Check out the license.
On a previous project I used to create one-off directives with isolated scopes instead of using ng-include. I only passed to them what was needed and it completely broke scope inheritance... on purpose. To me scope inheritance always felt dangerous and risky. If you use something inherited from scope, you are guaranteeing that you've broken encapsulation.
I'm not sure if I even knew the benefits when I did it, but it made refactoring amazingly easy. I also used a similar technique instead of ng-switch/ng-include combo which had the side effect of making the 1.08 to 1.2+ switch way easier because of the transclusion changes.
I definitely like that it the isolate approach really pushes the develop to think about encapsulation. I'm not opposed to scope inheritance, in general, and I think it definitely makes sense in the Controllers. But, I am kind of digging on the way scope isolation forces you to define HTML attribute hooks instead of relying on scope methods. This way, you can have different scope method names pipe into the same directive in different contexts. That's definitely a win.
I did a side-by-side comparison of this isolate-scope directive to the same thing, but without isolate-scope:
You can still get the same level of decoupling (pragmatically, not technically), but I think the isolate-scope syntax is actually nicer.
I realize that this was posted many months ago and you have probably figured this out by now, but the value of isolate scopes comes in when you reuse directives(which is really what they're intended for).
Let's say you want to create a reusable directive that says "Hello, Username" whatever the users name is. Sure you could just grab $scope.userName off the controllers scope, but if you're writing this directive for multiple developers, you don't know what variable that they're using in their controller, so you have them pass it in a property. Maybe one developer called it $scope.name and another one $scope.firstName. so your html for that directive just looks like this...
and for the other developer they'd use
Obviously a super simple useless example, but hopefully that will help someone out.
From what I've seen, isolate scopes don't necessarily relate to reusability at all. There's also nothing about an isolate scope that facilitates passing values in through attributes - you can easily pass both values and function references in through attributes and reference and invoke them, respectively, in non-isolate scopes.
From everything that I've been looking into, it seems the only true value of the isolate scope is when you are creating a directive that *transcludes* content AND provides *additional HTML* (to wrap the transcluded content). That way, the additional HTML can use its own isolated scope.
Outside of that, I wouldn't use isolate scope by default. In fact, doing so would lead to actual problems in an app:
I'm not trying to push back against the isolate scope - it serves a purpose. I'm only saying that the purpose is very specific and shouldn't be seen a general use-case.