How to conditionally apply a template via custom Angular directives?
Solution 1
You might be able to use a template
function. According to the docs:
You can specify template as a string representing the template or as a function which takes two arguments tElement and tAttrs (described in the compile function api below) and returns a string value representing the template.
function resolveTemplate(tElement, tAttrs) {
}
angular.module('MyApp').directive('maybeLink', function() {
return {
//...
template: resolveTemplate,
//...
};
});
Solution 2
I think this is the cleanest way to inject a dynamic template based on a scope property
angular.module('app')
.directive('dynamic-template', function () {
return {
template:'<ng-include src="template"/>',
restrict: 'E',
link: function postLink(scope) {
scope.template = 'views/dynamic-'+scope.type+'.html';
}
};
})
Solution 3
I came up with the following version at the end:
angular.module('MyApp').directive('maybeLink', function($compile) {
return {
scope: {
maybeLink: '=',
maybeLinkText: '='
},
link: function(scope, element, attrs) {
scope.$watch('maybeLinkText', function(newText) {
scope.text = newText.replace(/\n/g, '<br>');
});
scope.$watch('maybeLink', function() {
var newElement;
if (scope.maybeLink) {
newElement = $compile('<a href="#" ng-bind-html="text"></a>')(scope);
} else {
newElement = $compile('<span ng-bind-html="text"></span>')(scope);
}
element.replaceWith(newElement); // Replace the DOM
element = newElement; // Replace the 'element' reference
});
}
};
});
Solution 4
I would use ng-switch
.
something like
template: '<span ng-switch on="maybeLink">' +
' <span ng-switch-when="http://www.yahoo.com" ng-bind-html="text"></span>' +
' <a ng-switch-when="http://google.com" href="#" ng-bind-html="text"></a>' +
'</span>',
or
template: '<span ng-switch on="maybeLink">' +
' <span ng-switch-when={{maybeLink.length == 0}} ng-bind-html="text"></span>' +
' <a ng-switch-when={{maybeLink.length > 0}} href="#" ng-bind-html="text"></a>' +
'</span>',
So this is direction
Solution 5
You can use ng-if for the same
Below is the working example
Directive Code:
angular.module('MyApp').directive('maybeLink', function() {
return {
replace: true,
scope: {
maybeLink: '=',
maybeLinkText: '='
},
template: '<span>' +
' <span ng-if="!maybeLink.link" ng-bind-html="text"></span>' +
' <a ng-if="maybeLink.link" href="#" ng-bind-html="text"></a>' +
'</span>',
controller: function($scope) {
$scope.text = $scope.maybeLinkText.replace(/\n/g, '<br>');
}
};
});
Misha Moroshko
I build products that make humans happier. Previously Front End engineer at Facebook. Now, reimagining live experiences at https://muso.live
Updated on October 21, 2020Comments
-
Misha Moroshko over 3 years
Consider the following directive:
angular.module('MyApp').directive('maybeLink', function() { return { replace: true, scope: { maybeLink: '=', maybeLinkText: '=' }, template: '<span>' + ' <span ng-hide="maybeLink" ng-bind-html="text"></span>' + ' <a ng-show="maybeLink" href="#" ng-bind-html="text"></a>' + '</span>', controller: function($scope) { $scope.text = $scope.maybeLinkText.replace(/\n/g, '<br>'); } }; });
The directive adds both the
<span>
and the<a>
to the DOM (only one is visible at a time).How could I rewrite the directive such that it will add either
<span>
or<a>
to the DOM, but not both?
UPDATE
OK, I guess I could use
ng-if
like that:template: '<span>' + ' <span ng-if="!maybeLink" ng-bind-html="text"></span>' + ' <a ng-if="maybeLink" href="#" ng-bind-html="text"></a>' + '</span>'
But, how could one get rid of the surrounding
<span>
in this case?
UPDATE 2
Here is a version of the directive that uses
$compile
. It doesn't have the surrounding<span>
, but the two way data binding doesn't work either. I'm really interested to know how to fix the two way data binding issue. Any ideas?angular.module('MyApp').directive('maybeLink', function($compile) { return { scope: { maybeLink: '=', maybeLinkText: '=' }, link: function(scope, element, attrs) { scope.text = scope.maybeLinkText.replace(/\n/g, '<br>'); if (scope.maybeLink) { element.replaceWith($compile('<a href="#" ng-bind-html="text"></a>')(scope)); } else { element.replaceWith($compile('<span ng-bind-html="text"></span>')(scope)); } } }; });
-
Misha Moroshko almost 11 yearsThat's exactly what I came up with. See the UPDATE above. Any idea how to get rid of the surrounding
<span>
? -
demisx almost 10 yearsThis is the cleanest solution. I like it. As long as you don't need access to
scope
, of course. -
wendellmva over 9 yearsI used this one and got it up and running in seconds... why so many lines of code if you can achieve something short and sweet but simple and effective.. kudos well done
-
ctlacko over 9 yearsShould be accepted answer. I had to add an isolated scope (see the section under
scope
) to make my directive reusable for different components, but this is the answer to the question. -
GiM over 8 yearsThere is a huge difference between this solution and solution calling resolveTemplate, the first one is STATIC and cannot use actual model values. This one works great. What I wasn't aware is that partial included via ng-include, has proper access to the scope. Many thanks to the author of this
-
Karl Horky over 8 yearsThis looks great. How do you annotate it so that you can use this with annotations / ng-strict-di?
-
badera over 8 yearsThis is a very nice solution! However, I would like to use
transclude: true
and thenng-transclude
in the (dynamic) template. This does not work, angular raises an error:Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found.
Does anybody has an idea, how to solve even this?