My directive setup is as follows:
<div data-directive-a data-value="#33ff33" data-checked="true">
<div data-directive-b></div>
</div>
directiveB gets rendered.directiveA has a checkbox that is meant to change some value whenever it is checked.directiveA and directiveB's scope.I've managed to do this, but only by referencing $$prevSibling - is there a better way?
Here's the code: http://jsfiddle.net/janeklb/yugQf/ (in this sample, clicking the checkbox is simply meant to "clear" the value)
--
A bit more depth:
The 'contents' of directiveA (that which is being transcluded into it) isn't always directiveB. Other directiveB-like directives will end up in there as well. The directiveB "types" will always be used within directiveA.
To avoid coupling your components together too much, I would avoid using $$prevSibling. The best solution since your directiveB-like components are expected to be used within directiveA components is to use require.
.directive( 'directiveB', function () {
return {
require: '^directiveA',
scope: true,
link: function ( scope, element, attrs, directiveA ) {
scope.obj = directiveA.getObj();
}
};
})
The ^require indicates that somewhere on the element of this directive or on any element above it in the DOM hierarchy is a directive called directiveA, and we want to call methods on its controller.
.directive( 'directiveA', function () {
return {
// ...
controller: function ( $scope ) {
// ...
this.getObj = function () {
return $scope.obj;
};
}
};
})
So now in directiveB you can use ng-model="obj.attr".
There are many variations on this, but considering how general the question was, I feel this is the best approach. Here's an updated Fiddle: http://jsfiddle.net/yugQf/7/.
@Josh mentioned in his answer that
The best solution since your
directiveB-like components are expected to be used withindirectiveAcomponents is to userequire.
I've been toying with this and I believe a controller on directiveA is the only solution (so +1 Josh). Here's what the scopes look like using the OP's fiddle:

(Reverse the brown arrow and you have $$previousSibling instead of $$nextSibling.)
Other than $$previousSibling, scope 004 has no path to isolate scope 003. Note that scope 004 is the transcluded scope that directiveA creates, and since directiveB does not create a new scope, this scope is also used by directiveB.
Since the object you wish to share with directiveB is being created in directiveA's controller, we also can't use attributes to share data between the directives.
Creating a model inside a directive, and then sharing that model to the outside world is rather atypical. Normally, you'll want to define your models outside your directives and even outside your controllers (listen for a few minutes to Misko). Services are often a good place to store your models/data. Controllers should normally reference the parts of the model(s) that need to be projected into the view that they are associated with.
For simplicity, I'm going to define the model on a controller, then the directives will both access this model the normal way. For pedagogical purposes, directiveA will still use an isolate scope, and directiveB will create a new child scope using scope: new as in @Josh's answer. But any type (isolate, new child, no new scope) and combination will work, now that we have the model defined in a parent scope.
Ctrl:
$scope.model = {value: '#33ff33', checkedState = true};
HTML:
<div ng-controller="NoTouchPrevSibling">
<div data-directive-a data-value="model.value" data-checked="model.checkedState">
<div data-directive-b></div>
</div>
For other pedagogical reasons, I opted to pass directiveA the two model properties as separate attributes, but the entire model/object could also have been passed. Since directiveB will create a child scope, it doesn't need to pass any attributes since it has access to all of the parent/controller scope properties.
Directives:
app.directive('directiveA', function () {
return {
template: '<div>'
+ 'inside parent directive: {{checkedState}}'
+ '<input type="checkbox" ng-model="checkedState" />'
+ '<div ng-transclude></div>'
+ '</div>',
transclude: true,
replace: true,
scope: {
value: '=',
checkedState: '=checked'
},
};
});
app.directive('directiveB', function () {
return {
template: '<div>'
+ '<span>inside transcluded directive: {{model.checkedState}}</span>'
+ '<input type="text" ng-model="model.value" />'
+ '</div>',
replace: true,
scope: true
};
});
Scopes:

Note that directiveB's child scope (006) inherits from directiveA's transcluded scope (005).
After clicking the checkbox and changing the value in the textbox:

Note that Angular handles updating the isolate scope properties. Normal JavaScript prototypal inheritance gives directiveB's child scope access to the model in the controller scope (003).