Eli Weinstock-Herman

AngularJS vs Knockout - Templating (5 of 8)

Original post posted on Friday, October 11, 2013 at LessThanDot.com

I'm reviewing Angular and Knockout to determine which would fit better for a variety of upcoming projects. A huge piece of both of these frameworks is the ability to create and reuse templates for the output. AngularJS brings template Directives, transclusion, and behavior to the table, while Knockout brings templating, dynamic template names, and it's own ability to apply additional behavior.

This is the fifth of eight posts looking at the capabilities of knockout and Angular. In the introduction post, I outlined the capabilities that I am evaluating for. In the fourth post, I looked at serialization. This post explores some of the templating capabilities in the two frameworks.

All of the examples presented throughout the series are available in the tarwn/AngularJS-vs-Knockout repository on github.

Templating in AngularJS

Directives in Angular are touted as one of it's 'killer' features. They provide the ability to create entirely new elements using templates and custom behavior by replacing and manipulating DOM content. Angular Directives can be used to define custom behavior based on custom tags, attributes, and class names.

AngularJS Simple Templating Example

Full source available at Angular/SimpleTemplating.html.

Taking a username and turning it into a full twitter button seems like it could be a good case for templating. Creating a replacement element will move the actual code for the twitter button out of the flow, reducing repetition of the mess while making the original code just as readable (or even more so).

Code: html
<div ng-controller="TemplatingController">
    <h1>As an Element</h1>
    <twitter uservalue="{{ username }}"></twitter>
 
    <!-- ... attribute example, see source ... -->
</div>
<script type="text/javascript">
 
// ...
 
sampleApp.directive('twitter', function(){
    return {
        // must be an element
        restrict: 'E',
        // replace it
        replace: true,
        // local scope will have a one-way binding to username from the referencing scope
        scope: {
            user: '@uservalue'
        },
        // just a basic div instead of pulling
        template: '<iframe allowtransparency="true" frameborder="0" scrolling="no" src="//platform.twitter.com/widgets/follow_button.html?screen_name={{ user }}"  style="width:300px; height:20px;"></iframe>'
    }
});
 
// ...
</script>

The element is straightforward and readable. The directive will replace the custom element, pulling the value of the uservalue attribute in and adding that to the template HTML that will produce a twitter button when displayed.

AngularJS Templating with Transclusion

Full source available at Angular/TransclusionTemplating.html.

Angular directives provide the ability to transclude content, inserting content from a custom element into the template in a directive. I've worked with a number of layouts over the years that have container elements, transclusion is a great tool to replace this repetitive (and occasionally fragile) code with a more readable and less repetitive element that provides the same final product. Here I've defined a basic Directive that for a container with a title and transcluded content.

Code: html
<div ng-controller="TransclusionTemplatingController">
    <!-- don't try to use myAwesomeContainer, it won't work -->
    <my-awesome-container h1-title="{{ title }}">
        Here is my content: "{{ content }}"
    </my-awesome-container>
</div>
<script type="text/javascript">
    var sampleApp = angular.module('sampleApp', []);
 
    sampleApp.directive('myAwesomeContainer', function () {
        return {
            // must be an element
            restrict: 'E',
            // replace it
            replace: true,
            // use transclusion
            transclude: true,
            // local scope
            scope: {
                titleValue: "@h1Title"
            },
            // pretending we have a fancy set of HTML for the container
            template: '<div class="fancy-pants"><h1>{{ titleValue }}</h1><div ng-transclude></div></div>'
        }
    });
 
    sampleApp.controller('TransclusionTemplatingController', function ($scope) {
        $scope.content = "Some Dynamic Content";
        $scope.title = "A Dynamic Title";
    });
</script>

Defining dialogs, containers, and so on as standard templates could take a lot of repetitive code out of the source, provide a single touch point to modify the code for those containers, and reduce the size of the HTML file the end user is downloading.

AngularJS Templating with Behavior

Full source available at Angular/BehaviorTemplating.html.

In the validation post, I used the linking method of a Directive to build validation. With templates, we can use that same linking method to add behavior to the template. Taking the transcluded container one step further, I'm going to provide the ability to hide or show the content by clicking on the title of the container.

Code: html
<div ng-controller="BehaviorTemplatingController">
    <expanding-container clickable-title="{{ title }}">
        Here is my content: "{{ content }}"
    </expanding-container>
</div>
<script type="text/javascript">
    var sampleApp = angular.module('sampleApp', []);
 
    sampleApp.directive('expandingContainer', function () {
        return {
            // must be an element
            restrict: 'E',
            // replace it
            replace: true,
            // use transclusion
            transclude: true,
            // local scope
            scope: {
                titleValue: "@clickableTitle"
            },
            // pretending we have a fancy set of HTML for the container
            template: '<div class="fancy-pants"><div>{{ titleValue }}</div><div ng-transclude class="box-to-hide"></div></div>',
            link: function (scope, element, attributes) {
                // copied and reformatted from doc examples here: <a href="http://docs.angularjs.org/guide/directive">http://docs.angularjs.org/guide/directive</a>
                var opened = true;
                var title = angular.element(element.children()[0]);
                title.bind('click', toggle);
 
                function toggle() {
                    console.log("done");
                    opened = !opened;
                    element.removeClass(opened ? 'closed' : 'opened');
                    element.addClass(opened ? 'opened' : 'closed');
                };
 
                toggle();
            }
        }
    });
 
    sampleApp.controller('BehaviorTemplatingController', function ($scope) {
        $scope.content = "Some Dynamic Content";
        $scope.title = "Click title to toggle visibility";
    });
</script>

Ten to eleven years ago, this would have been an ASP or PHP function that took content, wrapped it in some HTML, then slathered on some cross-browser javascript. The combination of transclusion and linking script is pretty powerful, providing an easy way to create dialog, container control, or panel logic in a single place with behavior, then re-use it in a readable format via custom tags.

I ran into a couple major speedbumps while working on this, however. The first was that I started out going down the complete wrong road. By default, the guide and documentation on the Angular site default to the latest unstable version, while I am using the latest stable version. This resulted in quite a bit of confusion, as I was originally using a function that doesn't even exist in the stable version.

The other issue was the continued difficulty of debugging things that fail silently. While I was working on this, I often found myself staring at no results and no error message. It also took some fiddling to figure out which pattern to use when naming directives vs using them in the HTML. This is likely one of those things that you get used to as you use it heavily, but in the longer term could cause issues if you have to come back to it after to using it for a while or are bringing new developers up to speed with it for the first time.

Templating in Knockout

Templating in Knockout is a binding, and it works and is defined similar to how the other bindings work. Templates are defined as sections of HTML and bound to a data model from the template binding.

Knockout Simple Templating Example

Full source available at Knockout/SimpleTemplating.html.

Just as I did in the AngularJS example above, I'm going to start with a simple template that turns a username into a twitter button.

Code: html
<div>
    <h1>Inside an Element</h1>
    <div data-bind="template: { name: 'twitter-template', data: { user: username }}"></div>
    <!-- ... -->
</div>
<script type="text/html" id="twitter-template">
    <iframe allowtransparency="true" frameborder="0" scrolling="no"
        data-bind="attr: { src: '//platform.twitter.com/widgets/follow_button.html?screen_name=' + user() }"
        style="width:300px; height:20px;">
</iframe>
</script>

Templates in knockout fill the container they are defined on rather than replacing it. The data bind specifies the template name, which corresponds to a text/html block with the same id. The template binding specifies an anonymous object with the property as the viewmodel. This could as easily have been just the individual property, but I thought the anonymous object made it more similar to the Angular directive.

Knockout Nested Templating Example

Full source available at Knockout/NestedTemplating.html.

While knockout does not have the concept of tranclusion, it does have the ability to define templates dynamically using an observable or computed value for the template name. So, while knockout does not have a built-in ability to do transclusion, you can very easily pass along a sub-template name and viewmodel to render a nested template.

Code: html
<div data-bind="template: { name: 'container-template', data: { title: title, subtemplate: 'sample-contents', submodel: { content: content }}}"></div>
 
<script type="text/html" id="container-template">
    <div class="fancy-pants">
        <h1 data-bind="text: title"></h1>
        <div data-bind="template: { name: subtemplate, data: submodel}"></div>
    </div>
</script>
   
<script type="text/html" id="sample-contents">
    <div data-bind="text: content">        
    </div>
</script>
 
<script type="text/javascript">
var SimpleTemplatingModel = function () {
    this.title = ko.observable("A Dynamic Title");
    this.content = ko.observable("Some Dynamic Content");
};
 
// ...

Nesting templates dynamically is probably a less frequent use case than transclusion, and not something you would run into anywhere near as often as good uses for Angular's transclusion. But it does provide a neat example for dynamic templates, which have a much wider use case. You can easily build a SPA container using a high level template binding to an observable, then swap the value to new template names on demand (via routing).

Knockout Templating w/ Behavior Example

Full source available at Knockout/BehaviorTemplating.html.

Like Angular, knockout templates provide a mechanism to add behavior when they are applied. The template binding in knockout has additional properties that can be run as post-processing steps.

Code: html
<div data-bind="template: { name: 'container-template', data: { title: title, content: content }, afterRender: makeItToggly }"></div>
 
<script type="text/html" id="container-template">
    <div class="fancy-pants">
        <h1 data-bind="text: title"></h1>
        <div data-bind="text: content"></div>
    </div>
</script>
<script type="text/javascript">
    var SimpleTemplatingModel = function () {
        this.title = ko.observable("A Dynamic Title");
        this.content = ko.observable("Click title to toggle visibility");
    };
 
    var viewmodel = new SimpleTemplatingModel();
    ko.applyBindings(viewmodel)
 
    // some jQuery behavior stuff
    function makeItToggly(elem) {
        $(elem).click(function () {
            $(elem).find('div').toggle();
        });
    }
</script>

For this example, I've returned to the container that hides it's content when the title is clicked. Knockout does not include DOM manipulation methods, so I've reached out to jQuery for the click handling and toggling (though if the library size worries you, I could have also gone to Zepto.js, a lighter weight alternative).

Unlike the Angular example, there weren't any major struggles to make this work.

Some Differences

With templating, there are quite a few differences between the two frameworks, but I don't think that would prevent you from implementing the same site in either.

Readability

Looking at an AngularJS custom element vs a Knockout binding on an HTML element, the AngularJS one is definitely a little cleaner and easier to read. In my examples above, I could have trimmed down the Knockout template bindings a little more, but it still would have been more cluttered than a single named element, like AngularJS.

Dynamic Templating

This is a great feature in Knockout, and I could see a number of uses for it when you have a site with several pages, multiple sections in a page, sections that transform between read and edit mode, etc. I did try to implement something like this in Angular using Directives and defining them in a couple different ways from changeable properties in the scope, but was not able to get it working.

(Update)

When I originally published this post, my AngularJS focus was solely on custom directives, but it turns out () that there is a built-in ngInclude binding that will include a template similarly to knockout's dynamic template. It includes an external HTML file with a child scope that is derived from the current one. It also has an onload attribute, which is evaluated when the template is loaded.

Example: Angular/ngIncludeTemplating.html

It's not a huge difference, but I like Knockout's ability to specify the data model. With AngularJS, your scope is available to the template and it figures out what it wants to grab and manipulate, with knockout you can pass it an instance of exactly the viewmodel it's expecting and the template doesn't have to have any knowledge about the higher level viewmodel. It also differentiates between collections, which it will iterate over, and non-collections, which it will render one template against. On the plus side for Angular, it's templates for ngInclude are separate files already, something you have to add a plugin to knockout to achieve.

Transclusion

Transclusion is an excellent tool with a wide set of use cases. I'd love to see a library combine transclusion and dynamic templates, as I think that these are easily one of the biggest differentiators for templating in the two libraries.

Behavior

With the Knockout example I pulled in a third party library (jQuery), and this comes with all the potential maintenance issues that you get when you start adding additional libraries. I don't think the size is much of an issue, as I could just as easily have pulled in a smaller library. The other thing I didn't like about Knockout was defining the behavior as a binding on the element rather than as part of the template. In my example above, I wanted to build a component with some behavior, but I had to bind that extra behavior from outside the template definition. Beside feeling a little hinky, it also could cause some problems if I was using dynamic templates with different behavior.

The AngularJS example kept the behavior together with the template definition, which I liked, but jqLite doesn't have as extensive a set of functionality as a full standalone library like jQuery. If I needed that additional functionality, I don't think I can remove jqLite from the file size of AngularJS, so I'm carrying a little extra baggage.

Pain Points

I feel like I have to point out again the pain points I ran into with AngularJS and it's documentation. Even knowing that google is dropping me on the unstable branch and that the version is visible in the top left, in many cases it is very hard to go from a page in the unstable version to the corresponding page in a stable version, or to figure out what version a function was added so I can look for alternatives if it's not in stable yet. This caused a great deal of confusion. As far as I can tell, Knockout's documentation speaks to the latest stable/release version, and not the 3.0 version (beta when I started this, RC now). I do know that I haven't had any struggles with it, where I seem to struggle frequently with the Angular side (maybe they assume everyone is using the unstable version or has long enough deployment cycles that it will be stable by the time we deploy?).

Final Words

If we consider the end goal of using frameworks like these for templating, I feel that both bring a great deal of capability to the table. There are definitely differences in how they work and I felt those as I was working on the examples. I'd love to somehow have the transclusion ability from AngularJS, dynamic templating from Knockout, and combined behavior and template in a single place from AngularJS.

Knockout vs AngularJS

Comments are available on the original post at lessthandot.com