AngularJS Dynamic Templates – Yes We Can!

This is going to be a really fun post for me. AngularJS is a super powerful framework for building great applications but I love it when I figure out ways to bring the fashion to the functionality.

The Backstory

I am working on a project that allows the user to post various types of content to a timeline and essentially the data structure is the same but the styling varies quite a bit. As a result I needed to be able to style each content item according to its content type.

The problem is that that AngularJS does not have any logic built into its templates other than what you can get via binding. This is a good thing! A View should not have logic in it and only reflect the state of the Model. ViewModel I love you!

So how do get around this problem while keeping theoretical integrity in tact? $compile to the rescue!

Preview

http://onehungrymind.com/angular-dynamic-templates/

Source

https://github.com/simpulton/angular-dynamic-templates

The Application

So lets get started with the actual data model we are working with.

1
2
3
4
5
[
    {"content_type" : "image", "title" : "Image 00", "data" : "temp-photo.jpg"},
    {"content_type" : "video", "title" : "Video 00", "data" : "http://player.vimeo.com/video/37176398"},
    {"content_type" : "notes", "title" : "Notes 00", "data" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit."}
]

It is pretty straightforward data structure that shares title and data while differentiating itself on content_type.

And then we get the party started with a controller.

1
<div id="container" ng-controller="ContentCtrl"></div>

This next bit of code is for fetching the data and populating a content property that we are going to bind to in a template in just a moment.

Side note: I am using promises with my $http.get call and I love how concise and expressive promises make my code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function ContentCtrl($scope, $http) {
    "use strict";

    $scope.url = 'content.json';
    $scope.content = [];

    $scope.fetchContent = function() {
        $http.get($scope.url).then(function(result){
            $scope.content = result.data;
        });
    }

    $scope.fetchContent();
}

And now that we have populated $scope.content with meaningful data, we are going to go ahead an loop over it and stamp out “something”.

1
2
3
<div id="container" ng-controller="ContentCtrl">
    <content-item ng-repeat="item in content" content="item"></content-item>
</div>

In this case, I am creating a directive called contentItem that I have big plans for. Seriously, I am so excited I have to keep from spoiling where I am going with this.

Lets cover real quick the skeleton that I have put in place. I am restricting this to an element which is merely personal preference but it feels more like a self contained widget that way. I am also creating an isolated scope that is bound to the content property. Again, more self contained awesomeness. And there is the linker function which is where we are going to tie everything together at the end.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.directive('contentItem', function ($compile) {
    var linker = function(scope, element, attrs) {
        // DO SOMETHING
    }

    return {
        restrict: "E",
        rep1ace: true,
        link: linker,
        scope: {
            content:'='
        }
    };
});

So we need templates! I am going to pull a move from the BackboneJS/Underscore playbook and store them as strings in the directive. I have three template strings with one for each type of content I am dealing with.

1
2
3
4
5
6
7
app.directive('contentItem', function ($compile) {
    var imageTemplate = '<div class="entry-photo"><h2>&nbsp;</h2><div class="entry-img"><span><a href="{{rootDirectory}}{{content.data}}"><img ng-src="{{rootDirectory}}{{content.data}}" alt="entry photo"></a></span></div><div class="entry-text"><div class="entry-title">{{content.title}}</div><div class="entry-copy">{{content.description}}</div></div></div>';
    var videoTemplate = '<div class="entry-video"><h2>&nbsp;</h2><div class="entry-vid"><iframe ng-src="{{content.data}}" width="280" height="200" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe></div><div class="entry-text"><div class="entry-title">{{content.title}}</div><div class="entry-copy">{{content.description}}</div></div></div>';
    var noteTemplate = '<div class="entry-note"><h2>&nbsp;</h2><div class="entry-text"><div class="entry-title">{{content.title}}</div><div class="entry-copy">{{content.data}}</div></div></div>';    

    /* EDITED FOR BREVITY */
});

And then I have a simple utility function that returns the appropriate template based on type. This COULD be more concise but I opted for readability for illustration purposes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
app.directive('contentItem', function ($compile) {
    /* EDITED FOR BREVITY */

    var getTemplate = function(contentType) {
        var template = '';

        switch(contentType) {
            case 'image':
                template = imageTemplate;
                break;
            case 'video':
                template = videoTemplate;
                break;
            case 'notes':
                template = noteTemplate;
                break;
        }

        return template;
    }

    /* EDITED FOR BREVITY */
});

Behold! The Magic!

This is a one two punch. First, I get the appropriate template and add it to the DOM via element.html() and then show().

But! The newly minted template has not been endued with AngularJS powers yet. This is where we use the $compile service. And what exactly does $compile do? Thanks for asking! You are a lovely audience.

Compiles a piece of HTML string or DOM into a template and produces a template function, which can then be used to link scope and the template together.

Side note: there is also a property called rootDirectory that I have bene using lately to make it easier to switch environments depending on relative or absolute reference requirements for their assets.

1
2
3
4
5
6
7
8
9
10
11
12
13
app.directive('contentItem', function ($compile) {
    /* EDITED FOR BREVITY */

    var linker = function(scope, element, attrs) {
        scope.rootDirectory = 'images/';

        element.html(getTemplate(scope.content.content_type)).show();

        $compile(element.contents())(scope);
    }

    /* EDITED FOR BREVITY */
});

Wrap Up

And now we have seen behind the curtain of a way to dynamically create templates based on conditions and then shooting them up with performance enhancing AngularJS drugs on the fly.

Here is the directive in its entirety.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
app.directive('contentItem', function ($compile) {
    var imageTemplate = '<div class="entry-photo"><h2>&nbsp;</h2><div class="entry-img"><span><a href="{{rootDirectory}}{{content.data}}"><img ng-src="{{rootDirectory}}{{content.data}}" alt="entry photo"></a></span></div><div class="entry-text"><div class="entry-title">{{content.title}}</div><div class="entry-copy">{{content.description}}</div></div></div>';
    var videoTemplate = '<div class="entry-video"><h2>&nbsp;</h2><div class="entry-vid"><iframe ng-src="{{content.data}}" width="280" height="200" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe></div><div class="entry-text"><div class="entry-title">{{content.title}}</div><div class="entry-copy">{{content.description}}</div></div></div>';
    var noteTemplate = '<div class="entry-note"><h2>&nbsp;</h2><div class="entry-text"><div class="entry-title">{{content.title}}</div><div class="entry-copy">{{content.data}}</div></div></div>';

    var getTemplate = function(contentType) {
        var template = '';

        switch(contentType) {
            case 'image':
                template = imageTemplate;
                break;
            case 'video':
                template = videoTemplate;
                break;
            case 'notes':
                template = noteTemplate;
                break;
        }

        return template;
    }

    var linker = function(scope, element, attrs) {
        scope.rootDirectory = 'images/';

        element.html(getTemplate(scope.content.content_type)).show();

        $compile(element.contents())(scope);
    }

    return {
        restrict: "E",
        rep1ace: true,
        link: linker,
        scope: {
            content:'='
        }
    };
});

Feel free to grab the project and see what you can come up with.

Lukas.Over.and.Out!

Resources

The Source
https://github.com/simpulton/angular-dynamic-templates

$compile Documentation
http://docs.angularjs.org/api/ng.$compile

AngularJS Mailing List
https://groups.google.com/forum/?fromgroups#!forum/angular

AngularJS Dynamic Templates – Yes We Can!

43 Responses

  1. Why not just use ng-switch inside of your ng-repeat? Is there a specific reason you chose not to do it that way?

    In one of my projects, this is how I am doing it. ( This is a raw paste from the project ! )

    Ganaraj October 8, 2012 at 9:38 am #
  2. IT ate up my html!!!! damn…

    Ganaraj October 8, 2012 at 9:39 am #
  3. Haha… throw up a jsfiddle and we can compare.

    simpulton October 8, 2012 at 1:41 pm #
  4. Bravo Lukas! Great tut! Thanks for sharing

    J Castillo October 9, 2012 at 2:44 am #
  5. Thanks to you Lukas, I can now build a great app :-D
    Thanks a lot for sharing!

    Bakann October 9, 2012 at 11:47 am #
  6. Great post!

    I recently did something similar to distinguish between text, photo, and event posts. Instead of creating a new directive, however, I opted for a more simple approach using ng-switch (like Ganaraj mentioned). I’m not sure what the performance difference is, but I prefer ng-switch because it keeps all HTML within the template.

    Here’s a jsFiddle implementing the same functionality: http://jsfiddle.net/UhUP5/1/

    Sergio Pantoja October 12, 2012 at 12:09 am #
  7. Nice! This is a totally legitimate approach.

    I think the upside to encapsulating it in a directive the way that I did is maintainability and portability. If you had a bunch of content types, your HTML could get REALLY unruly very quickly.

    Also, it makes for easier composition of a view from template fragments especially if there were complex business rules around it.

    Thanks for the fiddle. It is definitely useful to see it done that way!

    simpulton October 12, 2012 at 12:15 am #
  8. Great post!
    I understand $compile() better now!
    Good to know the two approaches indeed!

    Stef October 12, 2012 at 8:51 pm #
  9. Hi! You can use the ‘ng-switch’ directive inside your template HTML, so that the HTML is encapsulated in the widget, but you still don’t have to play around with the $compile service. Additionally, you can make each content type template into a directive of its own, that way you can encapsulate the functionality of each content type.

    Another thing concerning your content loading in the controller: I can’t seem to find the documentation for it, but did you know that you don’t need the ‘fetchcontent’ function? Apparently, if a property in your scope is a Promise, AngularJS will update the property with the returned value once the Promise is resolved.
    This means you can write code like:
    $scope.content = $http.get(url).then(function (response) { return response.data; });
    And angular will update the ‘content’ property to contain the returned ‘data’ value.

    Schmulik Raskin October 15, 2012 at 9:19 am #
  10. Ha! A blog post in my blog post! I like it!

    Really great info Schmulik. Thanks for sharing!

    simpulton October 15, 2012 at 1:50 pm #
  11. Thanks for all these angular posts! They’re really helping me learn how to use the framework.

    Regarding dynamic templating, what happens if you set the template attribute of the directive to the template returned by getTemplate? Does that not also compile?

    Benny October 20, 2012 at 5:11 am #
  12. rocks!

    Aladdin Mhaimeed October 28, 2012 at 3:54 pm #
  13. Great tut!
    Helped me sort out an issue I was having with directives. Thanks.

    Chris Jones December 4, 2012 at 10:02 am #
  14. Hi..I’m really noob with angularjs and I can understand your code but I’ve 2 questions…inside the angularjs documentation, shows than the directive can receive a “compile function” function compile(tElement, tAttrs, transclude) { … } …using this would be similar to this example, where you create a link function and inject the compiler??,

    2) when is necessary call the show function?…I’ve found other directives examples and never call the show function…

    thanks and good luck…I’m waiting new articls :D

    angel December 11, 2012 at 5:13 am #
  15. Great article! I am still struggling with dynamic templates and was thinking about a directive approach so this is a great lead-in. Thanks also to your commenters, I must admit, i wasn’t across the ng-switch approach so that was also interesting.

    Michael Dausmann December 14, 2012 at 11:59 pm #
  16. Great Tutorial, please keep them coming.

    I have the same question as ‘angel’ above, what’s the purpose of the show function.

    Ali January 9, 2013 at 9:47 pm #
  17. Hi Angel and Ali –
    show() is actually a jQuery function which you can call on element because it is a jQuery object.

    More information can be found here http://api.jquery.com/show/

    It is REALLY convenient having jQuery baked into AngularJS like this because it makes tapping into jQuery super easy.

    I cover more of this here http://onehungrymind.com/for-the-designers/ Cheers!

    simpulton January 9, 2013 at 9:58 pm #
  18. Thank-you for a great post, I learned more about directives and $compile.

    I used another approach with keeping logic in the controller… I used an ng-include with its src that is bound to a property in the controller.

    Data comes in, updates property that defines the view needed in the scope , fires the bindings, changes the view which loads the proper html, then updates the data in the template… works well so far and no need for directive. Not that I don’t like directives, I like them! But just wanted to share another way of going about changing views on the fly.

    Al February 25, 2013 at 8:20 pm #
  19. Totally legit :D Thanks for sharing.

    simpulton February 25, 2013 at 8:51 pm #
  20. Not to be rude, but by combining ng-switch with ng-include you can do the exact same thing without having to inline html in your js. Ng-include takes an expression so you can do something like ng-include=”‘templateId | determineTemplate’”. If you don’t want to use a filter, you could use a scoped method as well. This gives you the same functionality you are trying to achieve here while allowing you to still build templates as normal.

    Keith March 5, 2013 at 4:36 pm #
  21. Not rude at all and I agree :) You COULD do that very thing and that would work in most cases. Where this approach would fall apart is if you had a LOT of variations you needed to account for. What would happen if you had 40 different content types you needed to render? That would very quickly become unmanageable and we are back to the jQuery DOM spaghetti problem of yesteryear. Or! what if you needed to dynamically compose a single DOM structure from multiple templates depending on rules set forth by business logic. The DOM would not be an appropriate place to make those decisions.

    There are cases where layout is the result of imperative operations and therefore should be extracted out of a declarative context.

    simpulton March 5, 2013 at 5:12 pm #
  22. Hey, it is an interesting solution. Have you given any thought to the performance impacts with larger data sets? Diving into the directive workflow and it’s inner working, it seems the reason for the split compile and linking function is for caching in the cases of ng-repeat nesting..

    Serge March 11, 2013 at 2:38 pm #
  23. I love your blog. The articles I have read so far are all helpful. Thank you for spending your precious time to guide us through the AngularJS wonderland.

    Regarding the use of

    1
    .show()

    in your linking function, I think Ali and Angel were saying that it was unnecessary.

    1
    element

    was never hidden in the first place, so it was supposed to show by default. I tried taking out

    1
    .show()

    and the app was still running fine.

    Colin March 15, 2013 at 7:05 am #
  24. Great article !!!

    I have been trying to do a similar thing where I generated input controls for forms. While the view is being rendered dynamically based on the scope input type, form validation seem to have some issues.

    I have created a small sample @ http://plnkr.co/edit/R3NTJK?p=preview

    I would really appreciate if some one could throw some insight on what exactly the issue is.

    Gautham March 15, 2013 at 11:45 am #
  25. Thank you, your tutorials are golden, and written very nicely for an Angular newcomer like me.

    I wondered if an adapted version of this method is what I’m after, any help would be greatly appreciated.
    I’d making a site with lots of questions. The plan is to write the questions as individual html files named “q1.html”, “q2.html” etc.

    So, is it possible to use the method here, but with existing html files (as with partials)?
    … and, if it is possible, is it a good idea or should I be working in a different way?
    (I initially tried using templateUrl in the directive, but couldn’t get it to work dynamically.)

    Cheers
    Chris

    Chris April 9, 2013 at 10:45 am #
  26. Chris – thanks for the kind words.

    In your case, I would take an entirely different approach. I would mark up all your questions as a JSON data structure and then write a single template to render the questions.

    For instance, have an array called $scope.questions and another property called $scope.currentQuestion and bind to that object ie {{ currentQuestion.question }} etc. From there you just have to write your functions to iterate the $scope.questions array such as $scope.nextQuestion and $scope.previousQuestion.

    Unless the layout for each question is varying wildly then doing a file for each question is way more work than is necessary. :D

    simpulton April 9, 2013 at 4:30 pm #
  27. Thank you, that sounds like a good idea. I’ve seen a few sample questions now and the layout differences should be manageable with your approach.
    Thanks
    Chris

    Chris April 10, 2013 at 3:23 pm #
  28. Did you guys test the same in IE, atleast for me it doesn’t work. Is it because of the Element directive is used?

    Srini April 28, 2013 at 6:57 pm #
  29. is it possible to define the templates in separate html files instead of string?

    gembin May 9, 2013 at 10:06 am #
  30. Thanks a lot. I landed on this post by chance and found some good explanations how to implement its own directives. I put into practice and it worked great! :)

    Fabien Udriot May 10, 2013 at 4:42 pm #
  31. I am following your example, but seems unclear to me is how do you bind two elements together? For instance if my content type are 2 select inputs and the selection in the first input changes thr options in the second select input, the how would I do this?

    anthony June 1, 2013 at 3:50 pm #
  32. I have a problem. The problem is identical to that described, but I have to appear about 10 000 entries. from the fact that at each iteration call $compile script runs very slowly. Any ideas how to speed up?

    Max June 14, 2013 at 9:42 am #
  33. hey, thanks for sharing this great article!

    Selman July 3, 2013 at 7:58 pm #
  34. One problem I’ve found with this approach is that you can’t select your $compiled elements from the DOM after they are compiled, which is troublesome.

    So if after your $compile(element.contents())(scope); statement you try console.log($(‘.entry-photo’).html()); … you get null.

    I have no idea why ^^

    geoidesic July 6, 2013 at 4:43 pm #
  35. Yup! Custom directives open up a whole world of possibilities. I’ve been replacing bucket loads of javascript code with directive calls in some legacy apps I’m working on. Been using AngularStrap and considering Bootstrap UI. Emberists just don’t get this at all!

    Peter Drinnan August 17, 2013 at 4:52 am #
  36. Thanks for the article. It really helped.

    Vikas September 4, 2013 at 3:15 pm #
  37. Hello, and thank you for this blog post :-)
    This describes exactly what i’m trying to do except for one important details I have all my templates in separated files. So I’m really interested by the answer you could give to “@gembin: is it possible to define the templates in separate html files instead of string?”

    meewii September 5, 2013 at 9:08 am #
  38. @gambin and @meewii To accomplish this in separate files you would need to use either the text plugin with RequireJS https://github.com/requirejs/text and inject your template as a string. I have used this technique on a large project and it was very effective. Or! You can also use Grunt as well via https://npmjs.org/package/grunt-angular-templates which I am using on another large project and it works just as well. Hope this helps!

    simpulton September 7, 2013 at 12:52 pm #
  39. .show() is not just unnecessary, but also breaks the logic

    John January 6, 2014 at 7:20 pm #
  40. What do you mean ‘breaks the logic’?

    simpulton January 8, 2014 at 12:48 pm #
  41. Thank you! They should add this as an example directive on the angular site…

    David Merritt January 21, 2014 at 2:19 am #
  42. Hey

    If i need a comment functionality as well then where should i define the function to post comment as the scope of this directive is different from the controller scope.

    Gagan February 9, 2014 at 7:57 pm #
Trackbacks/Pingbacks
  1. Learning Angular.js – A few resources - December 21, 2013

    […] Angular.js Dynamic Templates, by onehungrymind.com […]

Leave a Reply