Last Updated: September 09, 2019
·
95.06K
· devers

Variable templates for an AngularJS directive

Sometimes web services and APIs return different data sets for the same type of object. For example, the tumblr API will return a list of posts, but each post type has different data associated with it: text posts have a title and body, while photo posts have captions and images.

Since having different components and directives for each post type doesn't make much sense (especially when they're all displayed in one stream, as on tumblr), it's beneficial to be able to conditionally load a template based on the post type of each individual post.

This code is based on these two articles/posts:
https://github.com/angular/angular.js/issues/1039#issuecomment-10673465
http://onehungrymind.com/angularjs-dynamic-templates/

and here is the tumblr API for reference: http://www.tumblr.com/docs/en/api/v2

components.js

angular.module('components', []).
    directive('tumblrPost', ['$compile', '$http', '$templateCache', function($compile, $http, $templateCache) {

        var getTemplate = function(contentType) {
            var templateLoader,
            baseUrl = '/templates/components/tumblr/',
            templateMap = {
                text: 'text.html',
                photo: 'photo.html',
                video: 'video.html',
                quote: 'quote.html',
                link: 'link.html',
                chat: 'chat.html',
                audio: 'audio.html',
                answer: 'answer.html'
            };

            var templateUrl = baseUrl + templateMap[contentType];
            templateLoader = $http.get(templateUrl, {cache: $templateCache});

            return templateLoader;

        }

        var linker = function(scope, element, attrs) {

            var loader = getTemplate(scope.post.type);

            var promise = loader.success(function(html) {
                element.html(html);
            }).then(function (response) {
                element.replaceWith($compile(element.html())(scope));
            });
        }

        return {
            restrict: 'E',
            scope: {
                post:'='
            },
            link: linker
        };
    }]);

news.html (all posts view)

<h1 class="road">News</h1>

<ul id="news">
    <li ng-repeat="tumblrPost in posts">
        <tumblr-post post="tumblrPost"></tumblr-post>
    </li>
</ul>

<p class="read-more"><a href="http://{{posts[0].blog_name}}.tumblr.com/" target="_blank">Read more news</a></p>

text.html (tumblr text post component)

<a href="{{post.post_url}}" target="_blank" class="post-date">{{post.date}}</a>
<h3>{{post.title}}</h3>
<div ng-bind-html="post.body"></div>
<hr>

video.html (tumblr video post component, for comparison)

<a href="{{post.post_url}}" target="_blank" class="post-date">{{post.date}}</a>
<div ng-bind-html-unsafe="post.player[0].embed_code" ui-jq="fitVids"></div>
<div ng-bind-html="post.caption"></div>
<hr>

11 Responses
Add your response

Hi Devers,

Thanks for your nice example.
It's great helpful to me.
But, if I don't want to replace the whole html of element, how to?
I have try to using "element.html($compile(element.html())(scope))"
But it doesn't work.
Could you give me some hint?
Thank you.

over 1 year ago ·

Hi Devers,

I have knew how to do.
I just using "$compile(element.contents())(scope)" to replace "element.replaceWith($compile(element.html())(scope))"
Thank you.

over 1 year ago ·

Nice article, I was just working on something like this and this is showing me that Im on the right path.

Fran

over 1 year ago ·

Good read, I really like this pattern. It's pretty sweet!

over 1 year ago ·

Very useful, thanks ;)

over 1 year ago ·

Nice writeup. I'm making an angular directive where I can set the dynamic template. It works. Now I would love to update the template using broadcast events. The code works fine [1] and the new HTML is generated from $compile. The problem is that the directive does not 'refresh' the content. Any idea?

[1] https://gist.github.com/andreareginato/7171134

over 1 year ago ·

My Html binding for video template is not working Rest works like a charm :(

over 1 year ago ·

Using element for accessing the DOM e.g element.find(".child-class") does not work because of the element.replaceWith(). Any cleaner solution?

over 1 year ago ·

Nevermind. We can do something like this:


var compiled = $compile(response.data)(scope); element.replaceWith(compiled); element = compiled; </code> </pre> Thanks for the nice article though.
over 1 year ago ·

Awesome example! Why are you doing both success and then? Looks like almost the same is happening in there?

over 1 year ago ·

Thank you! This really helped me.

over 1 year ago ·