Reputation: 61
I'm trying to access a parent directive's controller method from the link function of the transcluding child directive, but have no luck with it. It works when I include the child as part of the parent's template or pass it in from a parent ng-controller. Shouldn't transclude create a child scope as well in angular 1.4?
Javascript:
var app = angular.module('app', []);
app.controller('AppController', function($scope) {
$scope.ctrlFn = function() {
alert('hello');
};
});
app.directive('outerDirective', function() {
return {
restrict: 'A',
scope: {
ctrlFn : '&'
},
controller: 'AppController',
transclude:true,
template: '<div ng-transclude></div>',
link: function(scope, element, attributes) {
scope.outerFunction = function() {
scope.ctrlFn();
};
}
};
});
app.directive('innerDirective', function() {
return {
scope: {
ctrlFn : '&'
},
replace:true,
template: '<button ng-click="innerFunction()">Child Directive</button>',
link: function(scope, element, attributes) {
scope.innerFunction = function() {
scope.ctrlFn();
};
}
};
});
HTML:
<div id="app" ng-app="app">
<div outer-directive ctrl-fn="ctrlFn">
<div inner-directive ctrl-fn="ctrlFn()"></div>
</div>
</div>
Upvotes: 2
Views: 1068
Reputation: 8308
Because you put scope: { ctrlFn: '&', }
on outerDirective
, it creates an isolate scope:
The 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent scope. This is useful when creating reusable components, which should not accidentally read or modify data in the parent scope. Note that an isolate scope directive without a
template
ortemplateUrl
will not apply the isolate scope to its children elements.
One use case for the isolate scope is to be able to scope.$watch
expressions on the scope from your linking function, which is useful even if your directive does not have a template. Another use for isolated scopes is to provide data for directives and interpolation in your directive’s template, if it has one.
However, this scope is ignored by transcluded children. When you transclude, you get a special transclusion scope which specifically ignores this isolate scope:
This scope is special, in that it is a child of the directive's scope (and so gets destroyed when the directive's scope gets destroyed) but it inherits the properties of the scope from which it was taken.
That means that the scope
passed to your link
function is only visible to your template. Anything that is transcluded will see your directive’s parent’s scope and ignore the isolate scope.
One way for directives to communicate with each other is require
. The require directive can inject a container directive’s controller into any child’s link
function. Additionally, require
searches ancestors via the DOM tree rather than the scope inheritence tree. So when you require a controller, transcluded content can get the controller of the directive that transcluded it. For example, your code can be adapted to use controllers this way:
JavaScript:
var app = angular.module('app', []);
app.controller('AppController', function($scope) {
$scope.ctrlFn = function() {
alert('hello');
};
});
app.directive('outerDirective', function() {
return {
restrict: 'A',
scope: {
ctrlFn : '='
},
controller: function OuterDirectiveController($scope) {
this.outerFunction = function () {
return $scope.ctrlFn();
};
},
transclude:true,
template: '<div ng-transclude></div>'
};
});
app.directive('innerDirective', function() {
return {
replace:true,
template: '<button ng-click="innerFunction()">Child Directive</button>',
link: function(scope, element, attributes, outerDirective) {
scope.innerFunction = function() {
outerDirective.outerFunction();
};
},
scope: {},
require: '^outerDirective'
};
});
Note I also changed the ctrlFn: '&'
to ctrlFn: '='
because '&'
is intended for when you want to execute an expression in the caller. For example, when you do something like <my-directive on-something-happened="x = $event.value"/>
. However, you are trying to pass down a function as a value instead of trying to pass down an executable angular expression.
Note I set scope: {}
on innerDirective
but I leave it empty. This indicates to AngularJS that I want an isolate scope—I don’t want to overwrite any values in the parent scope. Then in my link function, I populate scope.innerFunction
so that my template can access innerFunction
which references the required controller. I could also have just stored the controller directly on the scope too and saved a couple LOC for innerDirective
:
{
template: '<button ng-click="outerDirective.outerFunction()">Child Directive</button',
link: function (scope, element, attributes, outerDirective) {
scope.outerDirective = outerDirective;
},
// (other keys omitted for brevity).
}
HTML:
<div id="app" ng-app="app" ng-controller="AppController">
<div outer-directive ctrl-fn="ctrlFn">
<div inner-directive></div>
</div>
</div>
Also note that I changed your controller. Your AppController
is now connected to the root application element using ng-controller="AppController"
. This allows you to set the scope from your application controller. The directives each then act independently based on the data passed to them instead of being used as the root scope. I think that the usual use case for directives is reusable templates or to do anything that requires interacting with the DOM directly rather than being used once to sort of indirectly attach the root application controller.
Upvotes: 0