Get the Newsletter

Templating: Basics

A basic guide to using Aurelia's templating engine.

Introduction

Aurelia's templating system is simple to learn, and yet powerful enough to build even the most complex applications. This article will walk you through the construction of a static template, importing that template into parent templates, binding and manipulating data through the view-model, and the use of conditionals, repeaters, and events.

A Simple Template

All Aurelia templates must be wrapped with a <template> element. The most basic template is a component that prints "Hello World":

    
  <template>
    <p>
      Hello, World!
    </p>
  </template>
  
  

This template could be a page in an Aurelia application, or it could be the view for a custom element. A template containing only boilerplate could be useful as a footer containing copyright information or disclaimers, but where's the fun in a static template?

All Aurelia templates work with a view-model, so let's create one:

    
  export class Hello {
    constructor() {
      this.name = 'John Doe';
    }
  }
  
  

Let's bind the name property in our view-model into our template using Aurelia's string interpolation:

    
  <template>
    <p>
      Hello, ${name}!
    </p>
  </template>
  
  

One of the key features of Aurelia's templating system is helping to reduce context switching between your javascript code and your template markup. String interpolation using the \${} operator is a new feature in ES2015 that makes it simple to insert values into a string. Thus, Aurelia uses this standard syntax in your templates.

When this template is run, Aurelia will insert the value of the name property into the template where \${name} appears. Pretty simple, right? But what if we want logic in our string interpolation. Can we add our own expressions? Absolutely!

    
  <template>
    <p>
      ${arriving ? 'Hello' : 'Goodbye'}, ${name}!
    </p>
  </template>
  
  
    
  export class Greeter {
    constructor() {
      this.name = 'John Doe';
      this.arriving = true;
      setTimeout(
        () => this.arriving = false,
        5000);
    }
  }
  
  

In our template, when arriving is true, the ternary operator makes our result 'Hello', but when it's false, it makes our result 'Goodbye'. Our view-model code initializes arriving to true and changes it to false after 5 seconds (5000 milliseconds). So when we run the template, it will say "Hello, John Doe!" and after 5 seconds, it will say "Goodbye, John Doe!". Aurelia re-evaluates the string interpolation when the value of arriving changes!

But don't worry, there is no dirty-checking. Aurelia uses an observable-based binding system that reacts to changes as they happen without having to do dirty-checking. This means that Aurelia doesn't slow down as you add more complex functionality to your template and view-model.

Binding

Binding in Aurelia allows data from the view-model to drive template behavior. The most basic example of binding is linking a text box to the view model using value.bind. What if we let the user decide who they want to greet, and whether to say Hello or Goodbye?

    
  <template>
    <label for="nameField">
      Who to greet?
    </label>
    <input type="text" value.bind="name" id="nameField">
    <label for="arrivingBox">
      Arriving?
    </label>
    <input type="checkbox" checked.bind="arriving" id="arrivingBox">
    <p>
      ${arriving ? 'Hello' : 'Goodbye'}, ${name}!
    </p>
  </template>
  
  
    
  export class Greeter {
    constructor() {
      this.name = 'John Doe';
      this.arriving = true;
    }
  }
  
  

Above, we have a text box that asks for the name of the person to greet, and a checkbox indicating whether they're arriving. Because we defined name as "John Doe" in our view-model, the initial value of the text-box will be "John Doe", and with arriving set to true, our checkbox will start checked. When we change the name, the person we're greeting will change with it: "Hello, Jane Doe!". If we uncheck the box, the greeting will change: "Goodbye, Jane Doe!"

Notice that the way we set up the binding was by using value.bind and checked.bind. That . within the attribute is important: whenever you see the ., Aurelia is going to do something with that attribute. The most important thing to take away from this section is understanding that Aurelia will link attributes to the left of the . with actions to the right of the ..

You can learn more about data-binding in the Binding section of our docs.

Binding Focus

We can also use two-way data binding to communicate whether or not an element has focus:

    
  <template>
    <input focus.bind="hasFocus">
    ${hasFocus}
  </template>
  
  

When we click the input field, we see "true" printed. When we click elsewhere, it changes to "false".

Binding Scopes Using with

We can also declare that certain parts of our markup will be referencing properties of an object in the view model, as below:

    
  <template>
    <p with.bind="first">
      <input type="text" value.bind="message">
    </p>
    <p with.bind="second">
      <input type="text" value.bind="message">
    </p>
  </template>
  
  
    
  export class Hello {
    constructor() {
      this.first = {
        message : 'Hello'
      };
      this.second = {
        message : 'Goodbye'
      }
    }
  }
  
  

Using with is basically shorthand for "I'm working on properties of this object", which lets you reuse code as necessary.

Composition

In order to live by the DRY (Don't Repeat Yourself) Principle, we don't necessarily want to rely on tight coupling between our view and view-model pairs. Wouldn't it be great if there was a custom element that would arbitrarily combine an HTML template, a view-model, and maybe even some initialization data for us? As it turns out, we're in luck:

    
  <template>
    <compose view-model="hello"
              view="./hello.html"
              model.bind="{ target : 'World' }" ></compose>
  </template>
  
  
    
  <template>
    Hello, ${friend}!
  </template>
  
  
    
  export class Hello {
    activate(model) {
      this.friend = model.target;
    }
  }
  
  

Note that the view-model we're composing into has an activate method. When we use model.bind, the contents are passed to activate. We then pull the exact value that we need out of the passed model and assign it.

The as-element Attribute

In some cases, especially when creating table rows out of Aurelia custom elements, you may need to have a custom element masquerade as a standard HTML element. For example, if you're trying to fill table rows with data, you may need your custom element to appear as a <tr> row or <td> cell. This is where the as-element attribute comes in handy:

    
  <template>
    <require from="./hello-row.html"></require>
    <table>
      <tr as-element="hello-row">
    </table>
  </template>
  
  
    
  <template>
    <td>Hello</td>
    <td>World</td>
  </template>
  
  

The as-element attribute tells Aurelia that we want the content of the table row to be exactly what our hello-row template wraps. The way different browsers render tables means this may be necessary sometimes.

The View Resource Pipeline

The basic idea behind the View Resource Pipeline is that we're not limited to HTML or JavaScript. A basic example would be pulling in Bootstrap:

    
  <template>
    <require from="bootstrap/css/bootstrap.min.css"></require>
    <p class="lead">Hello, World!</p>
    <p>Lorem Ipsum...</p>
  </template>
  
  

Here, the <require> tag is taking a CSS file, instead of html or a view model. The View Resource Pipeline is the part of Aurelia that's responsible for recognizing that it's CSS, and handling it appropriately. One of the most powerful features of Aurelia is that the View Resource Pipeline is completely extensible, allowing you to define your own handler for any type of view resource you might want to define!

View and Compilation Spies

If you've installed the aurelia-testing plugin, you have access to two special templating behaviors:

    
  <template>
    <p view-spy compile-spy>Hello!</p>
  </template>
  
  

view-spy drops Aurelia's copy of the View object into the console, while compile-spy emits the compiler's TargetInstruction. This is especially useful for debugging any new View Resources you've created using the View Resource Pipeline.

Conditionals

Aurelia has two major tools for conditional display: if, and show. The difference is that if removes the element entirely from the DOM, and show toggles the aurelia-hide CSS class which controls the element's visibility.

The difference is subtle but important in terms of speed and usability. When the state changes in if, the template and all of its children are deleted from the DOM, which is computationally expensive if it's being done over and over. However, if show is being used for a very large template, such as a dashboard containing thousands of elements with their own bound data, then keeping those elements loaded-but-hidden may not end up being a useful approach.

Here's our basic "Hello World" that asks the user if they want to greet the world first:

    
  <template>
    <label for="greet">Would you like me to greet the world?</label>
    <input type="checkbox" id="greet" checked.bind="greet">
    <div if.bind="greet">
      Hello, World!
    </div>
  </template>
  
  

However, if we just want to hide the element from view instead of destroying it completely, we should use show instead of if.

    
  <template>
    <label for="greet">Would you like me to greet the world?</label>
    <input type="checkbox" id="greet" checked.bind="greet">
    <div show.bind="greet">
      Hello, World!
    </div>
  </template>
  
  

When unchecked, our "Hello World" div will have the aurelia-hide class, which sets display: none if you're using Aurelia's CSS libraries. However, if you don't want to do that, you can also define your own CSS rules that treat aurelia-hide differently, like setting visibility: none or height: 0px.

Conditionals can also be one-time bound, so that parts of the template are fixed when they're instantiated:

    
  <template>
    <div if.one-time="greet">
      Hello, World!
    </div>
    <div if.one-time="!greet">
      Some other time.
    </div>
  </template>
  
  
    
  export class ConditionalOneTimeTemplate {
    greet = (Math.random() > 0.5);
  }
  
  
    
  export class ConditionalOneTimeTemplate {
    greet = (Math.random() > 0.5);
  }
  
  

There's a 50-50 chance that we'll greet the world, or put it off until later. Once the page loads, this is fixed, because the data is one-time bound. Why don't we use show.one-time? If we think about what show does, it doesn't really make sense. We're saying we want a CSS class to be applied that will hide an element, and that it will never change. In most cases, we want if to refuse to create an element we'll never use.

Complementing if, there is else. Used in conjunction with if, else will render its content when if does not evaluate to true.

    
  <template>
    <div if.bind="showMessage">
      <span>${message}</span>
    </div>
    <div else>
      <span>Nothing to see here</span>
    </div>
  </template>
  
  

Elements using the else template modifier must follow an if bound element to make contextual sense and function properly.

Repeaters

Repeaters can be used on any element, including custom elements, and template elements too! Here are a few different data types that can be iterated with a repeater.

Arrays

Aurelia is also able to repeat elements for each element in an array.

    
  <template>
    <p repeat.for="friend of friends">Hello, ${friend}!</p>
  </template>
  
  
    
  export class RepeaterTemplate {
    constructor() {
      this.friends = [
        'Alice',
        'Bob',
        'Carol',
        'Dana'
      ];
    }
  }
  
  

This allows me to list out my friends and greet them one by one, rather than attempting to greet all 7 billion inhabitants of the world at once.

As mentioned before, we can also use the template element as our repeater - but we have to wrap it in a surrogate <template> element:

    
  <template>
    <template repeat.for="friend of friends">
      <p>Hello, ${friend}!</p>
    </template>
  </template>
  
  

Aurelia will not be able to observe changes to arrays using the array[index] = value syntax. To ensure that Aurelia can observe the changes on your array, make use of the Array methods: Array.prototype.push, Array.prototype.pop and Array.prototype.splice.

Two-way binding with arrays requires a special syntax due to the nature of the for-of loop in javascript. Do not use repeat.for="item of dataArray";doing so will result in one-way binding only - values typed into an input will not be bound back. Instead use the following syntax:

    
  <template>
    <div repeat.for="i of dataArray.length">
    <input type="text" value.bind="$parent.dataArray[i]">
    </div>
  </template>
  
  

Range

We can also iterate over a numerical range:

    
  <template>
    <p repeat.for="i of 10">${10-i}</p>
    <p>Blast off!</p>
  </template>
  
  

Note that the range will start at 0 with a length of 10, so our countdown really does start at 10 and end at 1 before blast off.

Sets

I can also use an ES6 Set in the same way:

    
  <template>
    <p repeat.for="friend of friends">Hello, ${friend}!</p>
  </template>
  
  
    
  export class RepeaterTemplate {
    constructor() {
      this.friends = new Set();
      this.friends.add('Alice');
      this.friends.add('Bob');
      this.friends.add('Carol');
      this.friends.add('Dana');
    }
  }
  
  

We can use repeaters with arrays, which is useful - but we can also use repeaters with other iterable data types, including objects, plus new ES6 standards such as Map and Set.

Map

One of the more useful iterables is the Map, because you can decompose your key and value into two variables directly in the repeater. Although you can repeat over objects straightforwardly, Maps can be two-way bound much more straightforwardly than Objects, so you should try to use Maps where possible.

    
  <template>
    <p repeat.for="[greeting, friend] of friends">${greeting}, ${friend.name}!</p>
  </template>
  
  
    
  export class RepeaterTemplate {
    constructor() {
      this.friends = new Map();
      this.friends.set('Hello',
        { name : 'Alice' });
      this.friends.set('Hola',
        { name : 'Bob' });
      this.friends.set('Ni Hao',
        { name : 'Carol' });
      this.friends.set('Molo',
        { name : 'Dana' });
    }
  }
  
  

One thing to notice in the example above is the dereference operator in [greeting, friend] - which breaks apart the map's key-value pair into greeting, the key, and friend, the value. Note that because all of our values are objects with the name property set, we can get our friend's name with \${friend.name}, just as if we were getting it from JavaScript!

Objects

Let's do the same thing, except with a traditional JavaScript object in our view-model:

    
  <template>
    <p repeat.for="greeting of friends | keys">${greeting}, ${friends[greeting].name}!</p>
  </template>
  
  
    
  export class RepeaterTemplate {
    constructor() {
      this.friends = {
        'Hello':
          { name : 'Alice' },
        'Hola':
          { name : 'Bob' },
        'Ni Hao':
          { name : 'Carol' },
        'Molo':
          { name : 'Dana' }
      }
    }
  }
  
  export class KeysValueConverter {
    toView(obj) {
      return Reflect.ownKeys(obj);
    }
  }
  
  

We just introduced something called a "value converter". Basically, we take the object in our view model, friends, and run it through our keys value converter. Aurelia looks for a class named KeysValueConverter and tries to call its toView() method with our friends object. That method returns an array of keys- which we can iterate. In a pinch, we can use this to iterate over Objects.

A common question pops up here: Why can't we just dereference the object into [key, value] like we did with Maps? The short answer is that JavaScript objects aren't iterable in the same way that Arrays, Maps, and Sets are. So in order to iterate over JavaScript objects, we have to transform them into something that is iterable. The way you approach it will change based on what exactly you want to do with that object. There is also a plugin that can be included, which will transform Objects to become iterable maps that can be dereferenced using the [key, value] syntax.