How to conditionally apply a template via custom Angular directives?

37,813

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

Plunker

Solution 5

You can use ng-if for the same

Below is the working example

Working Demo

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>');
    }
  }; 
});
Share:
37,813
Misha Moroshko
Author by

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, 2020

Comments

  • Misha Moroshko
    Misha Moroshko over 3 years

    DEMO

    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?

    DEMO

    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
    Misha Moroshko almost 11 years
    That's exactly what I came up with. See the UPDATE above. Any idea how to get rid of the surrounding <span>?
  • demisx
    demisx almost 10 years
    This is the cleanest solution. I like it. As long as you don't need access to scope, of course.
  • wendellmva
    wendellmva over 9 years
    I 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
    ctlacko over 9 years
    Should 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
    GiM over 8 years
    There 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
    Karl Horky over 8 years
    This looks great. How do you annotate it so that you can use this with annotations / ng-strict-di?
  • badera
    badera over 8 years
    This is a very nice solution! However, I would like to use transclude: true and then ng-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?