I'm trying to inject 2 templates into an element and operate on them:
<div
ic-first="foo"
ic-second="bar"
ic-third="baz"
ic-fourth="qux"
>
</div>
icFirst should inject via a template an empty div as a child of its element. icSecond should inject a second div (with a bunch of content) as the second child of its element, so the resulting html would look like:
<div
ic-first="foo" // priority: 100
ic-second="bar" // priority: 50
ic-third="baz" // priority: 0
ic-fourth="qux" // priority: 0
>
<div id="foo"></div>
<div> <!-- a bunch of stuff from the templateUrl --> </div>
</div>
Both icFirst and icSecond will inject other elements into the newly created containers.
When I specify a directive template property on both directives, I get an error:
Error: Multiple directives [icFirst, icSecond] asking for template on:
<div ic-first
…
When I add transclude: true
to both directives, icFirst executes just fine…but then the other directives on the same element are not executed. When I set transclude: 'element'
, the other directives execute but I get an error that the first child ($scope.firstObj
) is undefined.
All four directives need access to each other's scope, so I'm doing most of my work in their controllers:
app.directive('icFirst', ['ic.config', function (icConfig) {
return {
restrict: 'A',
priority: 100,
template: '<div id="{{firstId}}"></div>',
replace: false,
transclude: 'element',
controller: function icFirst($scope, $element, $attrs) {
// …
$scope.firstId = $scope.opts.fooId;
$scope.firstElm = $element.children()[0];
$scope.firstObj = {}; // this is used by the other 3 directives
},
link: function(scope, elm, attrs) { … } // <- event binding
}
);
app.directive('icSecond', ['ic.config', function (icConfig) {
return {
restrict: 'A',
priority: 0,
templateUrl: 'views/foo.html',
replace: false,
transclude: 'element',
controller: function icSecond($scope, $element, $attrs) {
// …
$scope.secondElm = $element.children()[1];
$scope.secondObj = new Bar( $scope.firstObj );
// ^ is used by the remaining 2 directives & requires obj from icFirst
},
link: function(scope, elm, attrs) { … } // <- event binding
}
);
Note I have corrected the behaviour of replace: false
to match the documented behaviour, as described in pull request #2433.
I tried instantiating $scope.firstObj
in the controller, and setting it in the linkFn (hoping the transclusion would have completed by the time the linkFn executes), but I get the same problem. It appears first-child is actually a comment.
... is quite illustrative as AngularJS doesn't allow multiple directives (on the same DOM level) to create their own isolate scopes. According to the documentation, this restriction is imposed in order to prevent collision or unsupported configuration of the $scope objects.
Essentially, transclusion in AngularJS is/was taking content such as a text node or HTML, and injecting it into a template at a specific entry point. This is now done in Angular through modern web APIs such as Shadow DOM and known as “Content Projection”.
The only reason I can come up with that explains throwing this error is that the AngularJS team was trying to avoid needless overwrites/DOM manipulation:
Considering the actual behaviour of replace: false
vs the documented behaviour, I think the actual is in fact the intended behaviour. If this is true, then allowing multiple templates/templateUrls to be used on the same element will cause subsequent templates to overwrite previous ones.
Since I already modified the source to match the documented behaviour, as a quick fix†, I modified the source again (/src/ng/compile.js:700) to remove the assertNoDuplicate
check (which corresponds to angular.js:4624
). Now I return the following 2 objects, and it works, and I can't find any negative repercussions:
// directive icFirst
return {
restrict: 'A',
priority: 100,
replace: false,
template: '<div id="{{firstId}}"></div>',
require: ["icFirst"],
controller: Controller,
link: postLink
};
// directive icSecond
return {
restrict: 'A',
require: ['icFirst'],
replace: false,
templateUrl: 'views/bar.html',
priority: 50,
controller: Controller,
link: postLink
};
† If made permanent, the check should probably beif (directive.templateUrl && directive.replace)
(and similar for directive.template)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With