This is probably basic level Angular JS but I haven’t seen it mentioned anywhere. I’m probably missing something fundamental about directive scope.
Say you’ve got this directive (JSFiddle):
angular
.module('app', [])
.directive('thing', function() {
return {
restrict: 'E',
replace: true,
template: '<div><input ng-model="vm.name"/> Name: </div>',
controller: function() {
this.name = '';
},
controllerAs: 'vm'
};
});
Using it once works great:
<div ng-app="app">
<thing></thing>
</div>
But if you use the directive multiple times, it becomes clear that the directive views all share the same controller:
<div ng-app="app">
<thing></thing>
<thing></thing>
<thing></thing>
<thing></thing>
<thing></thing>
</div>
Typing in the first textbox affects all of the other directive views, ie. they are all pointing to the same controller.
In fact, if you have different directives with the same controllerAs
value, you can see that the vm
instance for each directive is set to the last directive’s controller (JSFiddle):
angular
.module('app', [])
.directive('firstDirective', function() {
return {
restrict: 'E',
replace: true,
template: '<div>first directive: <pre></pre></div>',
controller: function() {
this.foo = 'Hi!';
},
controllerAs: 'vm'
};
})
.directive('secondDirective', function(){
return {
restrict: 'E',
replace: true,
template: '<div>second directive: <pre></pre></div>',
controller: function() {
this.bar = 'There?';
},
controllerAs: 'vm'
};
});
<div ng-app="app">
<first-directive></first-directive>
<second-directive></second-directive>
</div>
If you change the name of the controllerAs
alias - say to firstDirectiveVm
and secondDirectiveVm
- then the problem goes away, so Angular JS by default is setting vm
globally each time a directive uses controllerAs: 'vm'
, and going down the page, meaning the last vm
wins. This can obviously be a pretty tricky problem to diagnose. Besides which, this workaround of changing each directive’s controllerAs
value won’t work for multiple directives of the same type.
The solution is to set scope
to true
in the directive declaration (JSFiddle):
angular
.module('app', [])
.directive('thing', function() {
return {
restrict: 'E',
replace: true,
template: '<div><input ng-model="vm.name"/> Name: </div>',
controller: function() {
this.name = '';
},
controllerAs: 'vm',
scope: true
};
});
A lot more can happen in that scope
value than setting it to true. See the Angular JS docs for isolating directive scope for examples. Unfortunately, ‘scope’ seems to be an overloaded term in Angular JS world. This kind of ‘scope’ is talking about the scope of the element and attributes provided by the directive, in a way distinct from $scope
, which is what I’m trying to avoid by using controllerAs
in the first place.
It seems strange to me that shared scope is the default, and that you need to set scope
to a non-falsy value to opt out of that. I’m sure I’m missing a lot of nuance around the reasons. In any case, setting scope: true
seems to be the happy path.
I just wish I hadn’t wasted a full day rewriting an entire site before figuring out what was happening.
:-(