29

I would like to access a parent directive's scope, but I can't seem to get the right combination of settings. Is this possible and is it the right approach?

I really want to avoid putting something like SOME_CONST (which would help me make DOM updates through control flow) in MyCtrl

<div ng-controller="MyCtrl">
    <parent>
        <child></child>
    </parent>
</div>

var myApp = angular.module('myApp',[]);

function MyCtrl($scope) {
    $scope.obj = {prop:'foo'};
}

myApp.directive('parent', function() {
    return {
        scope: true,
        transclude: true,
        restrict: 'EA',
        template: '<div ng-transclude><h1>I\'m parent {{obj.prop}}<h1></div>',
        link: function(scope, elem, attrs) {
            scope.SOME_CONST = 'someConst';
        }
    }
});

myApp.directive('child', function() {
    return {
        restrict: 'EA',
        template: '<h1>I\'m child.... I want to access my parent\'s stuff, but I can\'t.  I can access MyCtrlScope though, see <b>{{obj.prop}}</b></h1> how can I access the <b>SOME_CONST</b> value in my parent\'s link function?  is this even a good idea? {{SOME_CONST}}.  I really don\'t want to put everything inside the MyCtrl',
    }
});

Please see this fiddle

Thanks

binarygiant
  • 6,362
  • 10
  • 50
  • 73

4 Answers4

42

With transclude: true and scope: true, the parent directive creates two new scopes: enter image description here

Scope 004 is a result of scope: true, and scope 005 is a result of transclude: true. Since the child directive does not create a new scope, it uses transcluded scope 005. As you can see from the diagram there is no path from scope 005 to scope 004 (except via private property $$prevSibling, which goes in the opposite direction of $$nextSibling -- but don't use those.)

@joakimbl's solution is probably best here, although I think it is more common to define an API on the parent directive's controller, rather than defining properties on this:

controller: function($scope) {
    $scope.SOME_CONST = 'someConst';
    this.getConst = function() {
       return $scope.SOME_CONST;
    }
}

Then in the child directive:

link:function(scope,element,attrs,parentCtrl){
    scope.SOME_CONST = parentCtrl.getConst();
},

This is how the tabs and pane directives work on Angular's home page ("Create Components" example).

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • @Mark Rajcok - Great answer Mark, you seem to be one of the few people on this site that actually understands Angular fully. I have a question similar to this one but at this link: [link](http://stackoverflow.com/questions/23437113/get-property-value-from-parent-directive-within-child-directive). My problem is that by the time the link method runs on the child directive the child directive's template has already been rendered. This means I can't use the parent property within the child to set scope variables prior to rendering. – EL MOJO May 03 '14 at 16:36
  • This is great but it seems that this value is copied rather than bound (ie. two-way data-binding) – Doug Amos Nov 02 '15 at 20:24
  • Agree, this isn't two-way data bound. To answer the original question, the child controller simply needs to access scope.$parent and it has the benefits of inheritance and two-way data binding. – CarbonDry Apr 11 '16 at 16:14
  • This is awesome nice and clean answer. – akokani Oct 22 '17 at 18:50
9

Normally the way you access a parent scope variable in a directive is through bi-directional binding (scope:{model:'=model'} - see the angular guide on directives) in the directive configuration), but since you're using transclusion this is not so straight forward. If the child directive will always be a child of the parent directive you can however configure it to require the parent, and then get access to the parent controller in the child link function:

myApp.directive('parent', function() {
  return {
    scope: true,
    transclude: true,
    restrict: 'EA',
    template: '<div ng-transclude><h1>I\'m parent {{obj.prop}}<h1></div>',
    controller: function($scope) {
        $scope.SOME_CONST = 'someConst';
        this.SOME_CONST = $scope.SOME_CONST;
    }
  }
});

myApp.directive('child', function() {
  return {
    restrict: 'EA',
    require:'^parent',
    scope:true,
    link:function(scope,element,attrs,parentCtrl){
        scope.SOME_CONST = parentCtrl.SOME_CONST;
    },
    template: '<h1>I\'m child.... I want to access my parent\'s stuff, but I can\'t.  I can access MyCtrlScope though, see <b>{{obj.prop}}</b></h1> how can I access the <b>SOME_CONST</b> value in my parent\'s link function?  is this even a good idea? {{SOME_CONST}}.  I really don\'t want to put everything inside the MyCtrl',
  }
});

See this update: http://jsfiddle.net/uN2uv/

joakimbl
  • 18,081
  • 5
  • 54
  • 53
6

I just had the same problem and finally solved it with the angular manual ;)

In short: you need to use a controller in your parent directive and require that controller in your child directive. This way you are able to get your parent properties.

See https://docs.angularjs.org/guide/directive Chapter: Creating Directives that Communicate

I changed your fiddle to use a controller, now you can access your constant: https://jsfiddle.net/bbrqdmt3/1/

var myApp = angular.module('myApp',[]);

function MyCtrl($scope) {
    $scope.obj = {prop:'foo'};
}

myApp.directive('parent', function() {
    return {
        scope: true,
        transclude: true,
        restrict: 'EA',
        template: '<div ng-transclude><h1>I\'m parent {{obj.prop}}<h1></div>',
        controller: function($scope) {
            this.getConst= function() {
                return 'someConst';
            }                        
        },
    }
});

myApp.directive('child', function() {
    return {
        restrict: 'EA',
        require : '^parent',
        link: function(scope, element, attrs, ctrl) {
            scope.value= ctrl.getConst();
        },
        template: '<h1>I\'m child.... I want to access my parent\'s stuff, but I can\'t.  I can access MyCtrlScope though, see <b>{{obj.prop}}</b></h1> how can I access the <b>SOME_CONST</b> value in my parent\'s link function?  is this even a good idea? {{value}}.  I really don\'t want to put everything inside the MyCtrl',
    }
});
firebean
  • 211
  • 3
  • 2
0

There's a transclude fn in the arguments of the link fn after the controller.

myApp.directive('parent', function() {
  return {
    scope: true,
    transclude: true,
    restrict: 'EA',
    template: '<div><h1>I'm a parent header.</h1></div>',
    link: function (scope, el, attrs, ctrl, transclude) {

        transclude(scope, function (clone, scope) {
            element.append(clone); // <-- will transclude it's own scope
        });

    },
    controller: function($scope) {
        $scope.parent = {
            binding: 'I\'m a parent binding'
        };
    }
  }
});

myApp.directive('child', function() {
  return {
    restrict: 'EA',
    require:'^parent',
    scope:true,
    link:function(scope,element,attrs,parentCtrl){

    },
    template: '<div>{{parent.binding}}</div>' // <-- has access to parent's scope
  }
});
Cameron
  • 2,427
  • 1
  • 22
  • 27