Angular.js directive dynamic templateURL

185,882

Solution 1

You can use ng-include directive.

Try something like this:

emanuel.directive('hymn', function() {
   return {
       restrict: 'E',
       link: function(scope, element, attrs) {
           scope.getContentUrl = function() {
                return 'content/excerpts/hymn-' + attrs.ver + '.html';
           }
       },
       template: '<div ng-include="getContentUrl()"></div>'
   }
});

UPD. for watching ver attribute

emanuel.directive('hymn', function() {
   return {
       restrict: 'E',
       link: function(scope, element, attrs) {
           scope.contentUrl = 'content/excerpts/hymn-' + attrs.ver + '.html';
           attrs.$observe("ver",function(v){
               scope.contentUrl = 'content/excerpts/hymn-' + v + '.html';
           });
       },
       template: '<div ng-include="contentUrl"></div>'
   }
});

Solution 2

emanuel.directive('hymn', function() {
   return {
       restrict: 'E',
       link: function(scope, element, attrs) {
           // some ode
       },
       templateUrl: function(elem,attrs) {
           return attrs.templateUrl || 'some/path/default.html'
       }
   }
});

So you can provide templateUrl via markup

<hymn template-url="contentUrl"><hymn>

Now you just take a care that property contentUrl populates with dynamically generated path.

Solution 3

Thanks to @pgregory, I could resolve my problem using this directive for inline editing

.directive("superEdit", function($compile){
    return{
        link: function(scope, element, attrs){
            var colName = attrs["superEdit"];
            alert(colName);

            scope.getContentUrl = function() {
                if (colName == 'Something') {
                    return 'app/correction/templates/lov-edit.html';
                }else {
                    return 'app/correction/templates/simple-edit.html';
                }
            }

            var template = '<div ng-include="getContentUrl()"></div>';

            var linkFn = $compile(template);
            var content = linkFn(scope);
            element.append(content);
        }
    }
})

Solution 4

You don't need custom directive here. Just use ng-include src attribute. It's compiled so you can put code inside. See plunker with solution for your issue.

<div ng-repeat="week in [1,2]">
  <div ng-repeat="day in ['monday', 'tuesday']">
    <ng-include src="'content/before-'+ week + '-' + day + '.html'"></ng-include>
  </div>
</div>

Solution 5

I had the same problem and I solved in a slightly different way from the others. I am using angular 1.4.4.

In my case, I have a shell template that creates a CSS Bootstrap panel:

<div class="class-container panel panel-info">
    <div class="panel-heading">
        <h3 class="panel-title">{{title}} </h3>
    </div>
    <div class="panel-body">
        <sp-panel-body panelbodytpl="{{panelbodytpl}}"></sp-panel-body>
    </div>
</div>

I want to include panel body templates depending on the route.

    angular.module('MyApp')
    .directive('spPanelBody', ['$compile', function($compile){
        return {
            restrict        : 'E',
            scope : true,
            link: function (scope, element, attrs) {
                scope.data = angular.fromJson(scope.data);
                element.append($compile('<ng-include src="\'' + scope.panelbodytpl + '\'"></ng-include>')(scope));
            }
        }
    }]);

I then have the following template included when the route is #/students:

<div class="students-wrapper">
    <div ng-controller="StudentsIndexController as studentCtrl" class="row">
        <div ng-repeat="student in studentCtrl.students" class="col-sm-6 col-md-4 col-lg-3">
            <sp-panel 
            title="{{student.firstName}} {{student.middleName}} {{student.lastName}}"
            panelbodytpl="{{'/student/panel-body.html'}}"
            data="{{student}}"
            ></sp-panel>
        </div>
    </div>
</div>

The panel-body.html template as follows:

Date of Birth: {{data.dob * 1000 | date : 'dd MMM yyyy'}}

Sample data in the case someone wants to have a go:

var student = {
    'id'            : 1,
    'firstName'     : 'John',
    'middleName'    : '',
    'lastName'      : 'Smith',
    'dob'           : 1130799600,
    'current-class' : 5
}
Share:
185,882

Related videos on Youtube

Alen Giliana
Author by

Alen Giliana

Updated on July 13, 2020

Comments

  • Alen Giliana
    Alen Giliana almost 4 years

    I have a custom tag in a routeProvider template that that calls for a directive template. The version attribute will be populated by the scope which then calls for the right template.

    <hymn ver="before-{{ week }}-{{ day }}"></hymn>
    

    There are multiple versions of the hymn based on what week and day it is. I was anticipating to use the directive to populate the correct .html portion. The variable is not being read by the templateUrl.

    emanuel.directive('hymn', function() {
        var contentUrl;
        return {
            restrict: 'E',
            link: function(scope, element, attrs) {
                // concatenating the directory to the ver attr to select the correct excerpt for the day
                contentUrl = 'content/excerpts/hymn-' + attrs.ver + '.html';
            },
            // passing in contentUrl variable
            templateUrl: contentUrl
        }
    });
    

    There are multiple files in excerpts directory that are labeled before-1-monday.html, before-2-tuesday.html, …

  • Alen Giliana
    Alen Giliana over 10 years
    Its great solution. Is there a way to write it that it can handle multiple instances? Currently, once the scope is set it does not recognize new attrs.ver.
  • pgregory
    pgregory over 10 years
    You mean, you want to watch ver attribute changes and rerender directive?
  • Alen Giliana
    Alen Giliana over 10 years
    Hey @pgregory, I will have more than one <hymn> tag in a template and I was planning to have the directive render them based on the ver tag. <hymn ver="before-1-monday"></hymn> ... <hymn ver="after-1-monday"></hymn> ... <hymn ver="seasonal-1-monday"></hymn>
  • pgregory
    pgregory over 10 years
    Thank you for clarifying. If you declare directive the way posted in upd., your use case when you using multiple <hymn ...> should work well. Or maybe its time to construct a prototype at jsfilddle?
  • Alen Giliana
    Alen Giliana over 10 years
    I tried to prototype into JSFiddle but I couldn't get it to link directives to external pages URL. At least you will see how it is formatted. sample page on my site
  • pgregory
    pgregory over 10 years
    Hello @AlenGiliana, Ive take a look at your site, and changed [JSFiddle](http://jsfiddle.net/JQgG5/6/). All you need is scope:{}` in directive declaration - scope isolation. Also I strongly recommend you to use last version of angular. <script type="text/ng-template" id="..."> - is local alternative to html pages
  • Alen Giliana
    Alen Giliana over 10 years
    Do you mean to use Angular 1.2.1? Thank you for the help by the way, this learning curve is insane :)
  • pgregory
    pgregory over 10 years
    Yes, using last stable versions of 3rd party libraries is good practice) At the moment angularjs 1.2.13 is last
  • josec89
    josec89 about 10 years
    Nice, but... can I access to scope attributes from the templateUrl function? The templateUrl depends on a scope value, but I can't access to it :(
  • Andrej Kaurin
    Andrej Kaurin about 10 years
    Nope. Maybe you should explain exactly what you need so we can find solution. It is probably that you can send everything you need via attrs.
  • josec89
    josec89 about 10 years
    I found the solution! :) I've created a directive called 'entry'. An entry has a 'type' (int, string, date...) and, depending on the type of the entry, you have one template or another (an input type='text', input type='number', some plugin for date....). The thing is that I pass the entry type through the scope and I couldn't use it to decide which template should be loaded. I used the method written in the other answer. I had problems accessing to the scope with ng-include (because it creates a new scope), but I solved it useing $parent. Thanks btw :)
  • Andrej Kaurin
    Andrej Kaurin about 10 years
    I am glad you found solution. I would NOT recommend directive having dependency on its parent unless it is controller set in require part of directive.
  • cfd_aero
    cfd_aero about 10 years
    Finally! Exactly what I was looking for! I didn't realize I had access to elem and attrs from a templateUrl function. THANKS!
  • Are Almaas
    Are Almaas almost 10 years
    Awesome. Would change the ng-include to a scope variable instead of a function. If it's a function it repeats alot of times. That's what happens to me atleast..
  • Lu4
    Lu4 almost 9 years
    templateUrl is called once per directive, it is not being called upon each directive instance initialization, be careful!!! It may be bug in angular though...
  • Ian Vaughan
    Ian Vaughan almost 9 years
    @Lu4 I have to disagree with that, as I am using the directive twice in a template with a different attribute value passed in, and it renders the different template as per the attribute correctly.
  • Lu4
    Lu4 almost 9 years
    I haven't checked it yet but according to my latest findings, It's probably worth mentioning that it's once per $compile phase. In other words if you use ng-repeat with your directive and want to set individual template based on specific ng-repeat item context, it won't work, because $compile phase walks through your directive once before actual ng-repeat happens. So in that meaning it's being called once...
  • Andi Giga
    Andi Giga almost 9 years
    With this solution my double bindings don't work anymore. The solution of Andrej Kaurin works for me.
  • Ashraf Sabry
    Ashraf Sabry over 5 years
    I wonder how this answer got all these upvotes? This won't work because the attrs object contains the attribute values as read from the DOM objects i.e., string values. The OP wanted to evaluate expressions against the scope. In your example, attrs.templateUrl value will be just the constant string 'contentUrl'. See this plunk next.plnkr.co/edit/N1FHOvaH7I5DVnmO