Select Page

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 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/demos/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.

[
    {"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 a 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.


<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.

function ContentCtrl($scope, $http) {
    "use strict";

<pre><code>$scope.url = 'content.json';
$scope.content = [];

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

$scope.fetchContent();
</code></pre>
}

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”.


<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.

app.directive('contentItem', function ($compile) {
    var linker = function(scope, element, attrs) {
        // DO SOMETHING
    }

<pre><code>return {
    restrict: "E",
    link: linker,
    scope: {
        content:'='
    }
};
</code></pre>
});

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.

app.directive('contentItem', function ($compile) {
    var imageTemplate = '

<div class="entry-photo">
<h2> </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> </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> </h2>
<div class="entry-text">
<div class="entry-title">{{content.title}}</div>
<div class="entry-copy">{{content.data}}</div>
</div>
</div>
';

<pre><code>/* EDITED FOR BREVITY */
</code></pre>
});

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.

app.directive('contentItem', function ($compile) {
    /* EDITED FOR BREVITY */

<pre><code>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 */
</code></pre>
});

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.

app.directive('contentItem', function ($compile) {
    /* EDITED FOR BREVITY */

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

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

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

/* EDITED FOR BREVITY */
</code></pre>
});

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.

app.directive('contentItem', function ($compile) {
    var imageTemplate = '

<div class="entry-photo">
<h2> </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> </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> </h2>
<div class="entry-text">
<div class="entry-title">{{content.title}}</div>
<div class="entry-copy">{{content.data}}</div>
</div>
</div>
';

<pre><code>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",
    link: linker,
    scope: {
        content:'='
    }
};
</code></pre>
});

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