Reputation: 288
example in: http://jsfiddle.net/avowkind/PS8UT/
I want a nested child directive to get its data from its wrapping parent directive if present, otherwise from the outer controller.
<div ng-controller="MyCtrl">
<parent index="1">
<child></child>
</parent>
<parent index="2">
<child></child>
</parent>
<h1>No Parent</h1>
<child></child>
</div>
<hr>
Desired output
Parent 1
Child of parent 1
Parent 2
Child of parent 2
No Parent
Child of parent 0
Currently my child object only sees the outer controller value:
Actual output
Parent 1
Child of parent 0
Parent 2
Child of parent 0
No Parent
Child of parent 0
This is the simple version; in reality the outer directives get data from a server that is formatted by the nested child so what is communicated is a complex object not a simple string. Furthermore the child is a visualisation that will work on different data sets so the outer parent directive is not always the same type.
More generally the pattern I am trying to get here is to have separate directives for populating the model and viewing it. so a more realistic usage would be
<temperature-for city="Auckland">
<plot/>
<analysis/>
</temperature-for>
<humidity-for city="Hamilton">
<plot/>
<analysis/>
</temperature-for>
<test-data>
<plot/>
</test-data>
Upvotes: 5
Views: 12242
Reputation: 1019
There are a couple different ways to do this but assuming that you truly want to use the parent scopes here is a solution to go along with your fiddle.
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.index = 0;
}
myApp.directive('parent', function () {
return {
transclude: true,
scope: {
index: '='
},
restrict: 'EA',
template: '<h2>Parent {{ index }}</h2>',
compile: function(tE, tA, transcludeFn) {
return function (scope, elem, attrs) {
elem.append(transcludeFn(scope)[1]);
};
}
}
});
myApp.directive('child', function () {
return {
restrict: 'EA',
scope: false,
template: '<p>Child of parent {{ index }}</p>'
}
});
You can see a fork of your fiddle here.
The idea is that by getting rid of the ngTranscludeDirective and manually creating the transclusion, you can link the transclusion with the scope of your choosing. Then you can append the result where ever you like in the element resulting from your directive's compilation.
The other main point is to make sure the child directive doesn't create a scope (at all, whether an isolate scope, transcluded scope, or new scope).
I think this will give you the results you're asking for.
NOTE: Study your scopes well, because tweaking these behaviors can have unexpected results.
For example, if you add a linking function to the child directive and it sets index to 5:
link: function(scope) {
scope.index = 5;
}
this will not affect scope.items
for children nested in the parent. However it WILL affect the an external parent scope (in this case MyCtrl's scope). Any directive not inside a parent directive will just keep altering the MyCtrl index.
However if you add a new property to scope in the child link function:
link: function(scope) {
scope.somethingElse = foo;
}
scope.somethingElse
will be available on the parent scope whether nested in a parent directive or not.
Upvotes: 2
Reputation: 7050
You'll need to transclude and fine grain it according to your parent directive scope, you can't do it using the ng-transclude
directive: http://jsfiddle.net/PS8UT/2/
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.index = 0;
}
myApp.directive('parent', function ($compile) {
return {
scope: {
index: '@'
},
restrict: 'EA',
transclude: true,
template: '<h2>Parent {{ index }}</h2>',
link: function (scope, elem, attrs, ctrl, transclude) {
transclude(scope, function(clone, s){
elem.append($compile(clone)(s)); // basically you need to reassign the inner child scope to your current isolated scope
});
}
}
});
myApp.directive('child', function () {
return {
//scope: true, // no need to create another scope since you want to use the parent
// scope: { }, // no index printed
restrict: 'EA',
template: '<p>Child of parent {{ index }}</p>'
}
});
Usually, when you are dealing with templates and transclusion, needing parent inheritage, is a pain. ng-transclude
won't use your immediate parent scope as the parent, it usually use your controller scope. It's stated in angular docs $compile docs:
transclude compile the content of the element and make it available to the directive. Typically used with ngTransclude. The advantage of transclusion is that the linking function receives a transclusion function which is pre-bound to the correct scope. In a typical setup the widget creates an isolate scope, but the transclusion is not a child, but a sibling of the isolate scope. This makes it possible for the widget to have private state, and the transclusion to be bound to the parent (pre-isolate) scope.
Upvotes: 0
Reputation: 703
A different approach which I personally have had great success using is to define the plot and analysis directives as isolate scopes, and then two-way bind the required input.
This way the directive are completely standalone components, with a explicit, defined interface. I personally made a plotting directive like this:
<plot data="countries['Auckland'].plot.data" options="countries['Auckland'].plot.options" legend="external" zoom="xy"></plot>
Scope would look like:
scope: {
data: '=',
options: '=',
zoom: '@?', // '?' indicates optional
legend: '@?',
}
This way there's no confusion what data is required for this component to work, and you can write documentation inside the directive for the desired input attributes.
All in all, this is a pattern which works very well for a large portion of use cases in AngularJS, i.e. whenever there is a case for reusability.
Edit: Just wanted to add to that: Looking at your HTML, there's absolutely no indication what those directives use, they could depend on anything (e.g. do they get all the data from a service? or do they depend on a parent scope? If so, what scope?)
Upvotes: 3