Accessing parent scope in directive when using controllerAs
Solution 1
When you are using the ControllerAs syntax, a property is created on the $scope
object that is an alias to your controller. for example, ng-controller="MainCtrl as vm"
gives you $scope.vm
. $scope
is implied in the HTML, so accessing vm.name
in the HTML is the same as accessing $scope.vm.name
in JavaScript.
In the controller, you could access either this.name
or $scope.vm.name
, they would be functionally equivalent. However, in other controllers, this
would refer to that specific controller, and thus this.name
would not work.
Therefore, in this case, you could access the property you want in the directive's controller by using $scope.vm.name
. http://plnkr.co/edit/WTJy7LlB7VRJzwTGdFYs?p=preview
However, you will probably want to also use ControllerAs syntax with the directive as well; in this case, I recommend that instead of using vm
for your controller names, you use a unique name that can help identify which controller you are referring to. MainCtrl as main
, and then referring to main.name
will be much clearer.
I do recommend using an isolate scope if possible, however, since it will allow you to completely eliminate the need to inject $scope
into your directives, and allow your directive to be self contained, and reusable.
Side note, bindToController: true,
does nothing if you are not using an isolate scope; when you are using an isolate scope, it creates properties on the isolated controller to match the scope passed in, allowing for you to access the passed in values without needing $scope
.
Solution 2
One option is to traverse the $scope
chain until you find vm.
app.directive('myDir', function() {
return {
restrict: 'E',
scope: true,
template: '<div>my directive</div>',
bindToController: true,
controller: function($scope) {
console.log($scope.name2); // logs 'bound to the controller scope'
console.log($scope.$parent.vm.name); // logs 'bound to the controller vm'
}
};
});
However, this can be really brittle and it smells a bit off.
A more sophisticated and thought-out approach is to bind a property of your controller's scope to your directive via a passed argument.
HTML:
<my-dir name="vm.name" />
JS:
app.directive('myDir', function() {
return {
restrict: 'E',
scope: {
name: "="
},
template: '<div>my directive</div>',
bindToController: true,
controller: function($scope) {
console.log($scope.name); // logs 'bound to the controller vm'
}
};
});
See plunkr
diplosaurus
Updated on June 10, 2022Comments
-
diplosaurus about 2 years
I currently have a directive that's using properties from the parent controller's scope:
.controller('MainCtrl', function($scope) { $scope.name = 'My Name'; }) .directive('myDirective', function() { return { scope: true, controller: function($scope) { console.log($scope.name); // logs 'My Name' } }; })
Now I'm moving over to
controllerAs
syntax in my controllers, but I don't know how to get a reference to the controller object in my directive's controller..controller('MainCtrl', function() { var vm = this; vm.name = 'My Name'; }) .directive('myDirective', function() { return { scope: true, controller: function($scope) { console.log(vm.name); // logs 'Undefined' } }; })
Here's a plunkr illustrating the issue.
I also found this article that's trying to explain something similar, but in this case he's just reusing the exact same controller.
-
diplosaurus almost 9 yearsSo this does works although it's a bit depressing that every property on the outer
vm
needs to be manually bound to the directive via an attribute, that could get messy. The thing I don't understand is why I have to use an isolate scope, an inherited one should still give me an access to the property. -
David L almost 9 yearsYou could certainly pass the entire scope instead of a single property if you so wished. That would accomplish what you need, although I'd be careful about exposing that much content from your controller to your directive. Too much exposure strongly couples your components and decreases their reusability (which is one of the current gripes about Scope/vm/Angular 1.x and one of the things we'll see change the most in Angular 2).
-
diplosaurus almost 9 yearsThis is what I needed - thank you. I did not think out the relationship of
vm
in the html really being$scope.vm
, meaning I can still access the properties further down the chain. I also like the advice on isolate scope - I thought isolate scopes would only be used sparing to prevent interactions rather than a way of being more explicit. -
Rishi Tiwari almost 8 yearsTo use my parent controller in the directive with ControllerAs, I can use $scope.vm...but then I'm harcoding vm in my directive...and lets say if I want to use it somewhere else too..I need to make sure that the alias of my parent controller is vm..otherwise it won't work..Is there any work around for this..?
-
Claies almost 8 years@rishitiwari in general, you should strive to make agnostic directives that have no dependency on their parent host. Instead of accessing the parent controller, you should accept the data your directive will operate against as arguments
-
Rishi Tiwari almost 8 years@Claies: So does this mean one should use isolated scope only while using ControllerAs to make your directive generic..?
-
Claies almost 8 yearsmy recommendation is to always use isolated scopes for directives, there is really very few reasons not to. however, if there is some reason you must use an inherited scope, you can use
$parent
to reference the parent controller, but this can be messy, especially if you start doing$parent.$parent.$parent
..... -
Saurabh Tiwari almost 8 yearsinstead of passing the whole scope you can chose to pass only the
alias
property. This way you still can have thing defined in your original scope with$scope
and these property will not be reflected in directive -
David L almost 8 years@SaurabhTiwari even if you pass the alias for scope instead of $scope, you will still be exposing the entire hierarchy.