Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Dynamic templateUrl in directives #1039

Closed
coli opened this issue Jun 12, 2012 · 48 comments
Closed

Dynamic templateUrl in directives #1039

coli opened this issue Jun 12, 2012 · 48 comments

Comments

@coli
Copy link

coli commented Jun 12, 2012

I'd like to be able to have templateUrl be configurable

eg

<my-directive templateUrl="hard-coded-path'>

Right now, this can be done via compile()

        compile:function(tElement, tAttrs, transclude) {
            var html = $templateCache.get(tAttrs.templateUrl);
            tElement.html(html);
        },

Problem is this only works for inline template, I'd like to be able to fetch it via network if it is not available yet.

Ideally, the delayed compile function associated with templateUrl processing in Angular should be exposed to directive writers.

Very ideally, I'd like to be able to

<my-directive templateUrl="{{scope.templateUrl}}">
@IgorMinar
Copy link
Contributor

not quite what you want but this will work:

myApp.directive('foo', function($http, $templateCache) {
  return {
    compile: function(tElement, tAtrrs) {
      $http.get(tAttrs.templateUrl, {cache: $templateCache}).success(function(html) {
        t.Element.html(html);
      });
    }
  }
});

the problem here is that the template is not compiled. to compile it you'd have to inject $compile service, compile the fetched html template. To link the compiled template with a scope, you'd have to return a linking fn from the compile fn that will keep a reference to the scope. Yeah, it's a lot of work, but it's doable. :-)

we'll consider this RFE post 1.0

@dcu
Copy link

dcu commented Jul 21, 2012

+1. many people is asking this feature so what are the main reasons to not implement it?

@casio
Copy link

casio commented Oct 9, 2012

+1

1 similar comment
@abdulazeezsk
Copy link

+1

@olostan
Copy link
Contributor

olostan commented Oct 31, 2012

Did someone completed solution @IgorMinar suggested?

I have similar problem: I have a directive, which content depends on some data, that should be loaded from resource (full problem is: I what validations/presentations of model elements loaded from backend. So I'll have kind of and type of field is loaded from backend, and only after it is loaded I can decide which template to compile...

For not the only way how to achieve that is try to implement idea, mentioned at @IgorMinar's comment.

@teintinu
Copy link

teintinu commented Nov 8, 2012

Hi Coli, maybe my fork help you: https://github.com/josefernandotolentino/angular.js

Use can use parameters in route templateUrl:

'use strict';


// Declare app level module which depends on filters, and services
angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives']).
  config(['$routeProvider', function($routeProvider) {

    $routeProvider.when('/view/:id', {templateUrl: 'partials/partial{{id}}.html', controller: MyCtrl2});

    $routeProvider.otherwise({redirectTo: '/view/1'});
  }]);

@pkozlowski-opensource
Copy link
Member

@josefernandotolentino you are talking about different templateUrl - the original issue was opened for directives while you are talking about templateUrl being used in a route definition...

@teintinu
Copy link

teintinu commented Nov 9, 2012

ok pkoz, for this reason I said "maybe".

@etchemkay
Copy link

Based on @IgorMinar 's comments above, I was able to quickly able to come up with a working solution. Now, all I want to know is whether this is the "right" solution, in terms of efficiency/correctness. Any "gotcha"s that I may have overlooked ?

Off-Topic: By the way, I just went from "heard of AngularJS for the first time" to "learning AngularJS" to "single-handedly fully re-wrote a moderately complex mobile application" in 3 weeks time (which took a team of three 2.5 months to do in jQMobile & Mustache) ! That says a lot for just how fantastic AngularJS is. Big fan right here.

Here's the code. It should be simple enough to imagine the rest of the (controller and template) code -

// activity items can be of several types .. post-photo, status-change, etc. (think facebook activity stream)
App.directive('activityItem', ['$compile', '$http', '$templateCache', function($compile, $http, $templateCache) {
  var templateText, templateLoader,
    baseURL = 'partial/tpl/',
    typeTemplateMapping = {
      photo:    'photo-activity-item.html',
      status: 'status-activity-item.html'
    };

  return {
    restrict: 'E',
    replace: true,
    transclude: true,
    scope: {
      type: '@type', title: '=', authorName: '=', avatar: '=', timeAgo: '=',
      statsLikes: '=', statsViews: '=' // some basic stats for your post
    },
    compile: function(tElement, tAttrs) {
      var tplURL = baseURL + typeTemplateMapping[tAttrs.type];
      templateLoader = $http.get(tplURL, {cache: $templateCache})
        .success(function(html) {
          tElement.html(html);
        });

      return function (scope, element, attrs) {
        templateLoader.then(function (templateText) {
          element.html($compile(tElement.html())(scope));
        });
      };
    }
  };
}])
<!-- simple bootstrap list for, say, all photo posts (hence the hardcoded @type) -->
<ul class="nav nav-list">
  <li ng-repeat="post in posts">
    <activity-item
      type="photo"
      title="post.postTitle"
      author-name="post.creatorName"
      time-ago="post.formattedCreatedTimestamp"
      stats-likes="post.likeCount" stats-views="post.viewsCount"
      stats-comments="post.threadCount"
      avatar="post.creatorAvatarPath" >
    </activity-item>
  </li>
</ul>

@tkrotoff
Copy link

@etchemkay I use a similar method to re-use a form (so no copy-paste) for editing and creating (CRUD), in this context it is editing and creating a calendar event:

app.directive('eventForm', function() {
  return {
    restrict: 'E',
    replace: true,
    templateUrl: '_form.html',
    scope: {
      event: '=' // event attribute is binded to the event object of the directive
    }
  };
});

Note the use of templateUrl inside the directive so you don't have to use $http.get.

<!-- _form.html -->
<input type="text" ng-model="event.title">
<textarea ng-model="event.description"></textarea>
[...]
<h1>Create new calendar event</h1>
<form>
  <event-form event="event"></event-form>
  [...]
</form>
<h1>Edit existing calendar event</h1>
<form>
  <event-form event="event"></event-form>
  [...]
</form>

For sure the use of ng-include would be better:

<h1>Create new calendar event</h1>
<form>
  <ng-include src="'_form.html'"></ng-include>
  [...]
</form>

Ideally I would prefer to use directives for JavaScript code and re-use HTML code through ng-include.

@jgrund
Copy link
Contributor

jgrund commented Dec 14, 2012

+1

3 similar comments
@ArturGajowy
Copy link

+1

@ryanzec
Copy link

ryanzec commented Dec 24, 2012

+1

@vizo
Copy link

vizo commented Jan 6, 2013

+1

@lrlopez
Copy link
Contributor

lrlopez commented Jan 20, 2013

I've proposed an implementation of dynamic templates using functions in #1849. Take a look, may be it fits your needs.

@sudhakar
Copy link

+1

1 similar comment
@hamtie
Copy link

hamtie commented Jan 29, 2013

+1

@danieljsinclair
Copy link

+1

@coli
Copy link
Author

coli commented May 7, 2013

Actually, this is now supported in 1.1.4

@coli coli closed this as completed May 7, 2013
@highvoltag3
Copy link

@coli could you share an example? the docs specify it's supported but every function I try or way I try it won't work 😢

Update: Nerver mind, to anyone if this is not working for you please make sure you are using 1.1.4.. (My bad)!!

@powtac
Copy link

powtac commented Jun 17, 2013

@coli how does the implemented solution looks like?

@coli
Copy link
Author

coli commented Jun 17, 2013

@powtac in place of template or templateUrl, provide a function :)

@powtac
Copy link

powtac commented Jun 17, 2013

Got it! Thanks!

@olostan
Copy link
Contributor

olostan commented Jun 17, 2013

And does that function's parameters are resolved by DI?

@coli
Copy link
Author

coli commented Jun 17, 2013

@olostan nope, check the angular source code

@jmeiss
Copy link

jmeiss commented Jul 2, 2013

+1

2 similar comments
@tschiela
Copy link

+1

@josipf
Copy link

josipf commented Jul 31, 2013

+1

@ruud
Copy link

ruud commented Aug 15, 2013

This is also possible for creating dynamic templates:
In your directive use:

template : '<div ng-include="getTemplateUrl()"></div>'

Now your controller may decide which template to use:

$scope.getTemplateUrl = function() {
  return '/template/angular/search';
};

Because you have access to your scope parameters, you could also do:

$scope.getTemplateUrl = function() {
  return '/template/angular/search/' + $scope.query;
};

So your server could create a dynamic template for you.

@uh-nmb
Copy link

uh-nmb commented Aug 21, 2013

I've been fighting with this for awhile, so I wanted to toss in the solution I came up with. I was looking to create a directive that changed an editor based on the selected type. Each type editor is its own directive with an isolated scope. I was having difficulty with the ng-include method (my loaded template ended up with a blank scope), and didn't want to remove the scope isolation from my sub-directives as it's needed elsewhere. There are clearly changes that need to be made (type validation, for example), but this gives a working solution.

  .directive('typeEditor', ['$compile', function($compile) {
        var def = {
                restrict: 'E',
                scope: {
                    type: "=",
                    action: "=",
                },
                link: function(scope, iElement, iAttrs, controller) {
                    scope.$watch('type', function(newType) {
                        if (newType != "" && newType != undefined) {
                            iElement.html('<' + newType + '-editor action="action"></' + newType + '-editor>');
                            $compile(iElement.contents())(scope);
                        }
                    })
                },
              };

    return def;
  }])

This would seem to be easily extensible for any level of dynamic template contents.

@olostan
Copy link
Contributor

olostan commented Aug 27, 2013

@uh-nmb interesting solution.... what problem could probably be, if type directive binds some event listeners, those binding could be not unbound when you change type.

I think its better to do something like:

                    var childScope;
                    scope.$watch('type', function(newType) {
                        if (newType != "" && newType != undefined) {
                            if (childChope) childScope.$destroy();
                            childScope = scope.$new();
                            iElement.html('<' + newType + '-editor action="action"></' + newType + '-editor>');
                            $compile(iElement.contents())(childScope);
                        }
                    })

@gregtzar
Copy link

+1

@gregtzar
Copy link

https://groups.google.com/forum/#!topic/angular/-tYAplxox68 contains a great workaround using ng-include.

@coli
Copy link
Author

coli commented Aug 30, 2013

Hmmm, people, this is already supported in the latest version of AngularJS.

Unsubscribing myself....

@gregtzar
Copy link

@coli As far as I know there is still no way to bind the templateUrl to scope. Which is probably what a lot of people (including myself) were +ing it for.

@coli
Copy link
Author

coli commented Aug 30, 2013

Open a new issue please :) this one is long closed.

@willemmulder
Copy link

As a last note, the ideal declarative way that the first poster describes, is certainly possible!

Only, instead of doing

<div {{directiveNameInScope}}></div>

You can do

<div loaddirective="directiveNameInScope"></div>

Using this simple directive here: https://github.com/willemmulder/loaddirective

Happy coding!

@kumarvishal
Copy link

+1

@Rush
Copy link

Rush commented Oct 31, 2013

I am considering using AngularJS and would like to swap its HTTP template fetching for something else:

function myTemplateProvider(templateUrl, locals, callback) {
   getTemplateAsynchronously(templateUrl, function(compiledTemplate) {
       callback(compiledTemplate, locals);
   });
}

Is something like this possible? How to provide my own provider for the HTML data. Sorry if it is the wrong place to ask, but it looks closest to what I could find.

@unluckypixie
Copy link

No-one seems to have provided an example of the new way to do this so here is a clock directive I wrote using using an optional "template-url" attribute (uses '/app/partials/clock.html' if no attribute is set).

This works in Angular 1.2.4:

Directive:

Directives.directive('clock', function() {
    return {
        restrict: 'E',
        replace: true,
        scope: {
            // Private scope so that the 1 second tick doesn't trigger all scope digest functions.
        },
        templateUrl: function(element, attr) { return attr.templateUrl ? attr.templateUrl : '/app/partials/clock.html' },
        controller: function($scope, $timeout) {
            $scope.$watch(function() { return new Date().getMinutes(); }, function (newValue, oldValue) {
                $scope.date = Date.now();
            }, true);

            setInterval(function(){
                $scope.$digest();
            }, 1000);
        }
    }
});

Example usage:

<clock template-url="/app/partials/footer-clock.html" />

The partial (/app/partials/footer-clock.html):

<span class="footer-clock">
    <span date-time> {{date | date: "MMMM"}} </span>
    <span date-time> <span class="bold"> {{date | date: "dd"}} </span> </span>
    <span date-time> | {{date | date: "h:mm"}} </span>
    <span date-time> <span class="bold"> {{date | date : "a" | lowercase}} </span> </span> 
</span>

@geoidesic
Copy link

Something that isn't mentioned above is that templateUrl makes an XHR request with Accept headers set to "application / json", this isn't great because it's telling the server that it wants json back when really it wants HTML back.

Is there a way to get templateUrl to set Accept headers to "text/html"?

@ghost
Copy link

ghost commented Apr 9, 2014

@unluckypixie the problem is that the original post is wanting to avoid the hardcoded string path for the templateUrl.

@reyraa
Copy link

reyraa commented Dec 14, 2014

ngInclude implements all $compile functionality itself, I think we can use it inside directive:

<my-directive ng-include="element.template" config="{element.config}"></my-directive>

template is dynamic and configs come as object. I've used this method for a popup with 4 different templates in a project.

@shhtephe
Copy link

@TR0L, I'm a newbie trying to learn SPA stuff and you saved me so much trouble with your solution! Thank you so much!

@praveen1228
Copy link

I have a Question, am new to angularJS," Here i have a directive inside having a template, in the template i was using a templateUrl for rendering the another template', Whats My issue is i Want access the form data inside the template which is called by templateUrl",..Your Answer made my day Thanks Praveen

@offwork
Copy link

offwork commented Jan 13, 2016

Why are given continuously example of the link:! None of these examples does not work when the templateUrl: is used. The templateUrl: for the templates is a complex and long code is required.

@reyraa
Copy link

reyraa commented Jan 17, 2016

There is a clean way for this:

assuming app is the angular module:

app.directive('theDirective', function () {
    return {
        templateUrl: function ($element, $attrs) {
              return $attrs.templateUrl || 'some/path/default.html'
        },
       link: function () {
           // here is the handler
       }
    }
});

kasperlewau added a commit to FalconSocial/angular-ui-router-modal that referenced this issue Feb 18, 2016
If we transition to an 'abstract' (read: dynamic) state
that can handle multiple different templates/controllers based
on some value returned to us by a controllerProvider/templateProvider -
we need to make sure we dont use the previous invokation(s) template.

As such, we need to check for the existence of `templateProvider` on
the original state. If that is a function, we need to make a second request
through $templateRequest and manually compile our template.

Related issues:

angular/angular.js#2095
angular/angular.js#1039
@pankajanupam
Copy link

+1

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.