The accepted solution will break a directive with replace: true
: HTML won't be replaced, specific CSS selectors in use will no longer work, etc.
I want my directive to dynamically change its template by watching a string received as attribute from the parent controller, so I used $compile
from this answer together with $observe
from this small part of an interesting tutorial, but alas it's not working, like shown in this plunkr.
If jQuery is included before AngularJS in the scripts, the replaceWith
call throws me the following error:
TypeError: Cannot read property 'ownerDocument' of undefined
But if I remove jQuery, forcing AngularJS to use its jqLite, the same part throws this error, making things clearer to a total jQuery agnostic like me:
TypeError: Failed to execute 'replaceChild' on 'Node': parameter 1 is not of type 'Node'.
Even if it's clear to me that I'm not passing a valid 'Node' type object to the replaceWith
, I don't know how to handle this situation, since I was expecting $compile
to do the job.
The only things I know are that the console.log(tplContent)
looks like this (a promise am I right?):
Object
{
config: Object
data: "<script type="text/ng-template" id="templateId.html">
↵ <p>TEMPLATE A</p>
↵</script>"
headers: function (d)
ng339: 10
status: 200
statusText: "OK"
}
while the console.log($compile(tplContent)(scope))
returns an array with that same object as first and only item:
[Object]
0: {
config: Object
data: "<script type="text/ng-template" id="templateId.html">
↵ <p>TEMPLATE A</p>
↵</script>"
headers: function (d)
ng339: 10
status: 200
statusText: "OK"
},
length: 1
I do really want to avoid using any of the following two fallbacks, have you got any idea of what I'm doing wrong here?
I know I could split the directive into two directives and ng-if
them like this:
(function() {
'use-strict';
angular.module('app')
.directive('dynamicTemplateA', dynamicTemplate);
DynTplCtrl.$inject = ['$http', '$templateCache', '$compile', '$parse'];
function dynamicTemplate($http, $templateCache, $compile, $parse) {
var directive = {
restrict: 'E',
templateUrl: 'template-a.html',
scope: {},
bindToController: {
tpl: '@',
i: '='
},
controller: DynTplCtrl,
controllerAs: 'dyntplctrl',
link: linkFunc
}
return directive;
function linkFunc(scope, el, attrs, ctrl) {}
}
DynTplCtrl.$inject = [];
function DynTplCtrl() {}
})()
(function() {
'use-strict';
angular.module('app')
.directive('dynamicTemplateB', dynamicTemplate);
DynTplCtrl.$inject = ['$http', '$templateCache', '$compile', '$parse'];
function dynamicTemplate($http, $templateCache, $compile, $parse) {
var directive = {
restrict: 'E',
templateUrl: 'template-b.html',
scope: {},
bindToController: {
tpl: '@',
i: '='
},
controller: DynTplCtrl,
controllerAs: 'dyntplctrl',
link: linkFunc
}
return directive;
function linkFunc(scope, el, attrs, ctrl) {}
}
DynTplCtrl.$inject = [];
function DynTplCtrl() {}
})()
and then in the controller.html
:
<div ng-repeat="i in [1,2,3]">
<dynamic-template-a ng-if="mainctrl.tpl === 'a'" tpl="{{mainctrl.tpl}}" i="i"></dynamic-template-a>
<dynamic-template-b ng-if="mainctrl.tpl === 'b'" tpl="{{mainctrl.tpl}}" i="i"></dynamic-template-b>
</div>
I also know I could use ng-include
like this:
(function() {
'use-strict';
angular.module('app')
.directive('dynamicTemplateA', dynamicTemplate);
DynTplCtrl.$inject = ['$http', '$templateCache', '$compile', '$parse'];
function dynamicTemplate($http, $templateCache, $compile, $parse) {
var directive = {
restrict: 'E',
template: '<div ng-include="dyntplctrl.getTemplateUrl()"></div>',
scope: {},
bindToController: {
tpl: '@',
i: '='
},
controller: DynTplCtrl,
controllerAs: 'dyntplctrl',
link: linkFunc
}
return directive;
function linkFunc(scope, el, attrs, ctrl) {}
}
DynTplCtrl.$inject = [];
function DynTplCtrl() {
var vm = this;
vm.getTemplateUrl = _getTemplateUrl;
function _getTemplateUrl() {
return 'template-' + vm.tpl + '.html';
}
}
})()
Credits to this question.
You need to change your code a bit while replacing the template:
el.html(tplContent.data);
$compile(el.contents())(scope);
This will replace the element's contents (though you need to handle sanitization here), and then compiles the template in the directive's scope.
Also, for testing, I've removed the <script>
tags from the template-a.html
, and template-b.html
.
Here is a forked plunker which has the changes mentioned above.
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