Learning Angular: What is the scope of your directive?
This might create quite some debugging headaches, especially in a larger application. Things don’t bind properly, have different data on the scope than you’d expect etc.. When you define your directives, you have to be really careful about their scope.
This article is part of my “Learning NG” series, presenting some of my adventures while learning Angular. Check out the series intro and other articles. Note, I’m an Angular newbie, so I’m more than happy for any kind of feedback and improvement suggestions from more experienced people than me.
The issue
Take the following two directives defined in your HTML as follows:
<first-directive></first-directive> <second-directive></second-directive>
Here’s how they look internally.
.directive('firstDirective', function() { return { restrict: 'E', template: '<div>First directive: {{ vm.msg }}</div>', controller: function() { var vm = this; vm.msg = 'Hi'; }, controllerAs: 'vm' }; }) .directive('secondDirective', function() { return { restrict: 'E', template: '<div>Second directive: {{ vm.msg }}</div>', controller: function() { var vm = this; vm.msg = 'Hello World'; }, controllerAs: 'vm' }; });
Nothing strange, right? Each of them defines a directive controller which sets a variable msg
. What do you think is the expected output?
Hmm..not really, right? Clearly you would expect to get two different messages, “Hi” and “Hello World”. If you open the inspector (of your Chrome Devtools) you might understand why:
As you can see, there’s only one scope defined. Take a look at the <html>
tag at the very top: Angular nicely marks the scope with the ng-scope
class. You can also visualize it with Batarang, the Angular.js Chrome Devtools extension.
To do so, simply activate the according “Show scope” option.
So the issue is quite clear, the directives don’t create a separate, isolated scope and thus, the vm
variable of the directive controller on <second-directive>
overwrites the vm
variable of the directive controller on <first-directive>
. Thus, two times “Hello World”.
Directive controllers don’t create a separate scope out of the box!
Interestingly, while a normal controller creates a scope, the directive controller doesn’t do so. It totally makes sense, but you might easily fall into this trap.
The solution
Directives can be forced to create an isolated scope. This is done by using the scope
property, in the simplest case by setting it to true.
.directive('secondDirective', function() { return { ... scope: true, ... }; });
And voilá..
Also the HTML reflects the change, now having three different scopes.
Background
Some more background around scopes in directives.
The scope
variable can either have true
or false
(default) as we’ve just seen, or you pass an object explicitly specifying which properties should be passed from outside into the directive (basically for configuring it).
angular.directive('myDirective', function(){ return { ... scope: { obj1: '=', obj2: '@', obj3: '&' } ... }; });
=
stands for data binding, it passes a reference to an object inside your directive. It keeps the expression in the attribute in sync with the isolated scope, thus allows for 2-way bindings.
<my-directive obj1='vm.someObject'></my-directive>
@
stands for interpolation. It is used with {{}}
and will always return a string.
<my-directive ob1='{{ vm.msg }}'></my-directive>
&
stands for expression. Within the directive it will be made available as a function, that, when called, executes the passed expression. Use it like
<my directive callback='vm.showAlert('Hi')'></my-directive>
..with your directive configuration object being defined for instance like this:
return { ... scope: { callback: '&' }, template: '<button ng-click='callback()'>Click</button>' ... }
You can also have different names for the scope properties that are used internally or passed externally:
scope: { obj1: '=object1', obj2: '&object2', obj3: '@object3' }
You would use obj1
in your directive template, while the user of your directive would use object1
on the HTML.
Conclusion
While this example is obviously a simplified version where it’s quite easy to track to the problem, believe me, I invested some good time in understanding why suddenly my sidebar suddenly didn’t display any data. Only after I realized that the it’s directive scope contained the data of the <header>
directive’s scope I started to get suspicious.
The learning from this is that you should pay attention to the scope of your directive. Whether you should isolate it or rather rely on the parents scope. An “attribute directive”, augmenting an existing element like a button or input box with additional features might not necessarily need to be isolated. Instead, you might expect an isolated scope when using more closed, complete widgets. Just think about it.
Reference: | Learning Angular: What is the scope of your directive? from our WCG partner Juri Strumpflohner at the Juri Strumpflohner’s TechBlog blog. |