Accessing parent scope in directive when using controllerAs

10,102

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

Share:
10,102
diplosaurus
Author by

diplosaurus

Updated on June 10, 2022

Comments

  • diplosaurus
    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
    diplosaurus almost 9 years
    So 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
    David L almost 9 years
    You 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
    diplosaurus almost 9 years
    This 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
    Rishi Tiwari almost 8 years
    To 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
    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
    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
    Claies almost 8 years
    my 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
    Saurabh Tiwari almost 8 years
    instead 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
    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.