27

Is it possible to change templateUrl on the fly by passing values in the directive's scope? I want to pass data to controller that will render the page based on the data that passed from the directive

something maybe that looks like that:

<div> 
   <boom data="{{myData}}" />
</div> 

.directive('boom', function {
        return {
            restrict: 'E',
            transclude: true,
            scope: 'isolate',
            locals: { data: 'bind' },
            templateUrl: "myTemplate({{boom}}})" // <- that of course won't work.
        }
    });
iLemming
  • 34,477
  • 60
  • 195
  • 309

7 Answers7

54

It is possible, but when your template to be loaded depends on some scope-data you can't use the directive's templateUrl property anymore and you will be obliged to use lower-level API, namely $http and $compile.

Roughly what you need to do (only possible in the linking function) is to retrieve template's content using $http (don't forget to involve $templateCache!) and then compile template's content "manually".

It might sound like it is a lot of work but in practice it is rather straightforward. I would suggest having a look at the ngInclude directive sources where this pattern is used.

Here is a skeleton of such a directive:

app.directive('boom', function($http, $templateCache, $compile, $parse) {
        return {
            restrict: 'E',
            link: function(scope , iElement, iAttrs) {                            
              var boom = $parse(iAttrs.data)(scope);
              $http.get('myTemplate'+boom, {cache: $templateCache}).success(function(tplContent){
                iElement.replaceWith($compile(tplContent)(scope));                
              });              
            } 
        }
    });

assuming that it would be used as <boom data='name'></boom>. Working plunk here: http://plnkr.co/edit/TunwvhPPS6MdiJxpNBg8?p=preview

Please note that I've changed attributes evaluation from {{name}} to attributes parsing since probably a template should be determined only once, at the beginning.

pkozlowski.opensource
  • 117,202
  • 60
  • 326
  • 286
  • yeah, i'm trying to play with ngInclude, but I can't find how to get value of local variable. How to get `data` in my case? I'm trying attrs.data, but it returns `undefined` – iLemming Jan 31 '13 at 18:21
  • Provided more info. I'm not sure if you really want to use interpolation of your attributes since it would mean that template can change dynamically as part of the $digest cycle. But if you really want to do so you would have to $observe attributes. – pkozlowski.opensource Jan 31 '13 at 18:45
  • I was using angular 1.0.8 and it may be worth mentioning that `$compile` fails when you didn't include jQuery before Angular.\ – HMR Oct 06 '13 at 13:59
  • you can't imagine how thankfull I am for this! I had a (similar) solution of my own, but combined with one of my directives, it just compiled two times... thank's so much! – AlexK Aug 01 '14 at 15:08
  • It seems to work also, if you use var boom = scope.data; instead of using $parse... Why should I resort to $parse instead? – andimeier Sep 29 '15 at 14:22
16

This is a new feature in Angular versions 1.1.4+ I just found out if I use the current unstable (1.1.5) you can pass a function into the template url of a directive. The second parameter of the function is the value of the attribute directive as shown below.

Here is a link to the unpublished docs showing the official change.

To use partials/template1.html as the template url from

Html:

<div sub_view="template1"></div>

Directive:

.directive('subView', [()->
  restrict: 'A'
  # this requires at least angular 1.1.4 (currently unstable)
  templateUrl: (notsurewhatthisis, attr)->
    "partials/#{attr.subView}.html"
])
EpiphanyMachine
  • 698
  • 1
  • 10
  • 8
4

I had similar problem

 return {
        restrict: 'AE',
        templateUrl: function(elm,attrs){return (attrs.scrolled='scrolled' ?'parts/scrolledNav.php':'parts/nav.php')},
        replace: true,

partnersSite.directive('navMenu', function () {
    return {
        restrict: 'AE',
        templateUrl: function(elm,attrs){return (attrs.scrolled='scrolled' ?'parts/scrolledNav.php':'parts/nav.php')},
        replace: true,
        link: function (scope, elm, attrs) {
            scope.hidden = true;
            //other logics
        }
    };
});
<nav-menu scrolled="scrolled"></nav-menu>
Karb
  • 327
  • 3
  • 12
2

I've changed the answer from pkozlowski.opensource a little.

From:

var boom = $parse(iAttrs.data)(scope);

To:

var boom = scope.data.myData

That worked for me and it's possible to use

<boom data="{{myData}}" /> 

in the directive.

  • Example forked from Plnkr above: [http://plnkr.co/edit/7BQxFEZ12Zxw9J9yT7hn](http://plnkr.co/edit/7BQxFEZ12Zxw9J9yT7hn?p=preview). Note this approach doesn't work with Angular 1.0.8 (as used above) as even though `iAttrs.data` has a value (which you can seen if you log the `iAttrs` object), it's `undefined` when you try to access it. – GFoley83 Jul 03 '14 at 00:29
2

This question will be fixed with ng-include as follow:

MyApp.directive('boom', function() {
    return {
      restrict: 'E',
      transclude: true,
      scope: 'isolate',
      locals: { data: 'bind' },
      templateUrl: '<div ng-include="templateUrl"></div>',
      link: function (scope) {
        function switchTemplate(temp) {
          if (temp == 'x')
          { scope.templateUrl = 'XTemplate.html' }
          else if (temp == 'y')
          { scope.templateUrl = 'YTemplate.html' }
        }
      }
    }
});

Call the switchTemplate function with arbitrary temp parameter in the link function of directive.

Iman Bahrampour
  • 6,180
  • 2
  • 41
  • 64
1

This is a followup answer that addresses a few issues with previous answers. Notably, it will only compile templates once (which is important if you have a lot of these on your page, and it will watch for changes to the template after it has been linked. It also copies class and style from the original element to the template (although not in the very elegant way angular does internally when you use "replace: true". Unlike the current angular supported method of using a function for template or templateUrl, you can use scope information to determine the template to load.

.directive('boom', ['$http', '$templateCache', '$compile', function ($http, $templateCache, $compile) {
    //create a cache of compiled templates so we only compile templates a single time.
    var cache= {};
    return {
        restrict: 'E',
        scope: {
            Template: '&template'
        },
        link: function (scope, element, attrs) {
            //since we are replacing the element, and we may need to do it again, we need
            //to keep a reference to the element that is currently in the DOM
            var currentElement = element;
            var attach = function (template) {
                if (cache[template]) {
                    //use a cloneAttachFn so that the link function will clone the compiled elment instead of reusing it
                    cache[template](scope, function (e) {
                        //copy class and style
                        e.attr('class', element.attr('class'));
                        e.attr('style', element.attr('style'));
                        //replace the element currently in the DOM
                        currentElement.replaceWith(e);
                        //set e as the element currently in the dom
                        currentElement = e;
                    });
                }
                else {
                    $http.get('/pathtotemplates/' + template + '.html', {
                        cache: $templateCache
                    }).success(function (content) {
                        cache[template] = $compile(content);
                        attach(template);
                    }).error(function (err) {
                        //this is something specific to my implementation that could be customized
                        if (template != 'default') {
                            attach('default');
                        }
                        //do some generic hard coded template
                    });
                }
            };

            scope.$watch("Template()", function (v, o) {
                if (v != o) {
                    attach(v);
                }
            });
            scope.$on('$destroy', function(){
                currentElement.remove();
            });
        }
    };
} ])
Joe Enzminger
  • 11,110
  • 3
  • 50
  • 75
0

Those answers are good, but not professional. There is a syntax of using templateUrl, which we don't use often.It can be a function which returns a url.That function has some arguments. If you want more here is a cool article

http://www.w3docs.com/snippets/angularjs/dynamically-change-template-url-in-angularjs-directives.html

Hazarapet Tunanyan
  • 2,809
  • 26
  • 30
  • That's not the issue at heart. If you actually create a demo app similar to the OP's, you'll see that you can't simply pass the interpolated value to the directive's `templateURL` function. – Ceasar Nov 10 '15 at 20:38