Aurelia is an open-source UI JavaScript framework designed to create single page applications (SPAs) that doesn't behave like a framework. It's been built from the ground up using modern tooling and ECMAScript 2016 with full support for TypeScript. Its architecture is a series of collaborating libraries and WebComponents woven together with plain modern JavaScript. Aurelia is designed to help you create applications for the browser, mobile devices, or the desktop.
The Aurelia website (https://aurelia.io/) documents all the benefits it provides, so we'll only mention a few that we really like. Aurelia adheres to a “convention over configuration” philosophy that helps developers follow established patterns by reducing the amount of code required to build an application. It's easy to learn but that doesn't mean it lacks robustness or power. Developers only need to learn a small set of patterns before they can begin making significant progress on their own application. Aurelia has been designed to be extensible, which makes it an ideal choice for working with third-party libraries and frameworks. Aurelia is backed by Durandal Inc., which was established to provide commercial and enterprise Aurelia support resources, assuring customers that help is there if they need it.
Aurelia adheres to a “convention-over-configuration” philosophy that helps developers follow established patterns and helps them reduce the amount of code required to build their applications.
Modern Tooling
Aurelia is built from the ground up using modern tooling. It uses Node.js and is designed to work with package mangers like jspm (JavaScript Package Manager) for dynamic module loading and transcompiling ECMAScript 6 (ES6) code to ECMAScript 5 (ES5). Aurelia applications can be written the same way, but it isn't a requirement. There's support for using TypeScript and Webpack as an alternative. Getting a project set up is perhaps the most laborious part, but the Aurelia team is working on a CLI so that future set ups will be much easier.
This article goes the traditional route and uses jspm with SystemJS. If you don't have NodeJS installed, download it from http://node.org. The remaining tooling is installed using Node's package manager npm, including jspm.
Once you have NodeJS installed, open a command prompt and type the following to install jspm:
npm install - g jspm
GIT must be installed because jspm has a dependency on GIT to query GitHub for packages. Download GIT from https://git-scm.com. We also recommend creating a GitHub account if you don't already have one. GitHub has a rate limit for anonymous API requests, so it's best to configure jspm to use your GitHub credentials or personal access token. Type the following from a command prompt, and then follow the prompts to register your GitHub account with jspm:
jspm registry config github
Next, consider how the app is going to run locally. There are three options.
The first option uses a simple NodeJS Web server. The following command installs an http server globally:
npm install http-server -g
There's nothing to serve at the moment. We'll spin up the server later.
The second option uses Visual Studio and opens the project as a Website. From Visual Studio's menu, select File > Open > Open Website. Navigate to the project's folder and click Open.
The third option uses Firefox. Firefox allows serving the app directly from your hard drive.
With the tooling in place, you can start your first Aurelia app from scratch.
Hello Aurelia!
Create an empty folder called HelloAureila and open a command prompt to that location. Initialize jspm using the following command:
jspm init
Follow the series of questions to set up jspm and press Enter for each question to accept the defaults. Use the following two commands after jspm has initialized to load the base Aurelia framework and its bootstrapping library:
jspm install aurelia-framework
jspm install aurelia-bootstrapper
Open the config.js
using your favorite IDE, and add the following two strings to babelOptions
(about line 5):
"es7.decorators",
"es7.classProperties"
You should have the following:
...
babelOptions: {
"optional": [
"runtime",
"optimisation.modules.system",
"es7.decorators",
"es7.classProperties"
]
},
...
To start writing code, add a new index.html
file to the root folder and replace the default with Listing 1.
Listing 1: index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello Aurelia</title>
<meta name="viewport"
content="width=device-width, initial-scale=1">
</head>
<body aurelia-app>
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>SystemJS.import('aurelia-bootstrapper');</script>
</body>
</html>
This is the only pure html file needed when using Aurelia. The rest of the app consists of components. Two important items to note about this file are the script tags and the aurelia-app attribute within the <body>
element. The first script file loads SystemJS
, which is responsible for loading modules and transcompiling code form ES6 to ES5. The second script file includes the configuration settings for SystemJS
. The third script file loads Aurelia's bootstrapper library using SystemJS's import method. Once the bootstrapper is loaded, it scans the index.html
file for the aurelia-app attribute. This informs Aurelia where to inject the root app component.
Aurelia uses convention over configuration. The first convention it executes looks for the App component. Components are central to Aurelia and they all follow a View/View-Model pattern where a html file is used for the view and a JavaScript file for a view-model. Having the same name binds the files together. The bootstrapper looks for app.js
and app.html
, so go ahead and create both in the root folder. Listing 2 contains the app.js
file and Listing 3 contains the app.html
file.
Listing 2: app.js
export class App {
header = "Hello Aurelia!";
}
Listing 3: app.html
<template>
<h1>${header}</h1>
</template>
If you're using a NodeJS http server to run locally, now's the time to spin up the server:
http-server - o - c-1
If you're using Visual Studio, select View in Browser to run the app. If you're using Firefox, open the index.html
file to run the app.
Admittedly that was a lot of work just to execute six lines, but this little bit of code demonstrates the elegance that is Aurelia. The app.js
file is just an ES6
class. Although ES6 is the future of JavaScript, you get to use it now, thanks to SystemJS.
Although ES6 is the future of JavaScript, you get to use it now, thanks to SystemJS.
The App class has a single property called header. Its value is displayed in the app.html
using Aurelia's string interpolation. String interpolation is one of the methods used to render data from the view-model to its view. Notice how the contents of the html file are wrapped in a <template>
element. Aurelia uses the Web Component standard to construct its views. This is the essence of an Aurelia component - a JavaScript class and a Web Component.
Routing
Now that you have the Hello World app out of the way, let's start to address some real world concerns when building applications. This usually begins with setting up navigation.
Using the app.js
and app.html
files, create two copies of each of the files. Name the first set home.js
and home.html
and name the second set binding-demo.js
and binding-demo.html
. Open the home.js
file, and rename the class from App to Home, to match Listing 4.
Listing 4: home.js
export class Home {
header = "Hello Aurelia!";
}
Next, open binding-demo.js
and rename the class from App to BindingDemo. Also change the header string to Binding Demo. Your file should look like Listing 5.
Listing 5: binding-demo.js
export class BindingDemo {
header = "Binding Demo";
}
These components serve as the applications pages, and you can now modify the App class to configure routing. Replace the App class in app.js
with the code in Listing 6 and in the app.html
with the code in Listing 7.
Listing 6: app.js (routing)
export class App {
configureRouter(config, router) {
config.title = 'Aurelia';
config.map([
{ route: ['','home'],
name: 'home',
moduleId: 'home',
nav: true,
title:'Home' },
{ route: 'bindingdemo',
name: 'bindingdemo',
moduleId: 'binding-demo',
nav: true,
title:'Binding Demo' }
]);
this.router = router;
}
}
Listing 7: app.html (routing)
<template>
<ul>
<li repeat.for="route of router.navigation">
<a href.bind="route.href">${route.title}</a>
</li>
</ul>
<div>
<router-view></router-view>
</div>
</template>
Refreshing the page reveals a stunning example of a stellar UX. OK, it is not all that stunning, but what you do have is a basic example of routing with Aurelia. The App class uses another convention by implementing the configureRouter
callback method, which is passed a configuration object and a router object. The first thing set is the configuration's title
property, which sets the document's title element. Next is the route mapping. The map
method takes an array of route
objects with the properties found in Table 1.
The last line creates a public property on the App class to the router
object. This makes it available for binding in the view, which is now the master page for the application. The <ul>
element constructs links to each component using Aurelia's repeat.for attribute. It uses the expression route of router.navigation to create an <li>
element for each item in the router.navigation array. Think of it as a for...of loop. Everything inside the <li>
element is within the scope of the loop and route is the local variable as it iterates through the loop. The link element demonstrates Aurelia's binding syntax and is another example of string interpolation. Binding is accomplished by applying .bind
to any HTML attribute. We'll take a closer look at this shortly in the Databinding section.
The final piece to the master page is the custom element <router-view>
that determines where the component's view is rendered. A word of caution: If you leave out this element and its view-model is used to configure routing, only a blank page is rendered and no errors are logged to the console.
Routing isn't limited to the root component. Any component can configure its own child routing. Also, routing has a pipeline that can have methods injected into it. The best use-case for this feature is authentication. Both of these features are beyond the scope of this article, but are worth sharing in case you decide to explore further on your own.
Databinding
Databinding is a must for any UI framework and Aurelia really shines with its implementation. You've seen how to bind the href attribute on a link element by appending .bind
to the end of href
. That's it! Just append .bind
to any HTML attribute. The following is an example of binding a firstName
property on a view-model to the value
attribute on a Form input.
<input type="text"
value.bind="firstName"/>
Using .bind
Aurelia determines the appropriate binding based on the element and the attribute to which it is applied. Most of the time, one-way binding is applied and the flow of data is from the view-model to the view. For Form controls, two-way binding is applied. With two-way binding, data flows in both directions allowing changes made in the control to be passed back to the view-model. A third binding option is one-time. This only passes from the view-model to the view once and won't be monitored for any changes. The default binding is overridden by replacing .bind
with .one-way
, .two-way
, or .one-time
. This is rarely needed, but the option is there.
The best way to explore binding is to see it in action. Open the binding-demo.html
file and add the following markup:
<input type="text"
value.bind="header"
placeholder="change header"/>
Refresh the page and experiment with changing the header with two-way binding. When you're done, replace the header markup with the following:
<h1 textcontent.one-time="header"></h1>
Refresh the page again and notice that the header is no longer updating. The header value is being changed, but the binding engine is not updating the header element. Replace .one-time with .one-way and it will return to the same binding type as before when using string interpolation. You can also bind to the inner HTML of any element by using the following snippet:
<h1 innerhtml.bind="header | sanitizeHTML"></h1>
Notice the use of sanitizeHTML
. This is an Aurelia value converter. As the name suggests, a value converter takes the value of a binding as an input and transforms it into something else. The output is returned and bound to the target. In this example, any potentially dangerous markup, like script tags, is removed. To see this in action, place the following markup after the input tag:
<p>
<textarea rows="5" cols="40"
value.one-way="header | sanitizeHTML">
</textarea>
</p>
Refresh the page and try to add a script element <script></script>
to the first input. Notice that the header
property does display the script element but the <textarea>
does not. When binding any HTML content, sanitizeHTML
or an equivalent third-party library is recommended.
Now expand the demo to change the color of the header. In the binding-demo.js
file, add the following snippet after the header
property:
headerColors = [
{colorValue:"#000000", colorName:"BLACK" },
{colorValue:"#FF0000", colorName:"RED" },
{colorValue:"#00FF00", colorName:"GREEN" },
{colorValue:"#0000FF", colorName:"BLUE" }
];
currentColor = this.headerColors[0];
The headerColors
property is an array of custom color objects with properties for a color's value and a friendly name. The currentColor
property is used for binding a color object to the header in the view. By default, it's set to the first item in the headerColors
array. To apply this binding to the header, replace the <h1>
element with the following snippet:
<h1 css="color:${currentColor.colorValue}"
innerhtml.bind="header | sanitizeHTML">
</h1>
This adds the css attribute with a binding to the colorValue
property of the currentColor
object. The css attribute is an alias to the style attribute and is the legal way to use string interpolation for binding. Refreshing the page now won't show any difference because it's bound to the same color as its default. Add the following markup to the bottom of binding-demo.html
to add the ability to change the header's color:
<p>Set Header Color</p>
<ol>
<li repeat.for="color of headerColors">
<label css="color:${color.colorValue}">
<input type="radio"
name="headercolor"
model.bind="color"
checked.bind="$parent.currentColor" />
${color.colorName}
</label>
</li>
</ol>
Aurelia's repeat.for
is being used to loop through the array headerColor
s and create radio buttons list items. Take note of the model.bind
and checked.bind
expressions. Use model.bind
with objects or array of objects when binding to radio button, checkboxes, or drop-down lists. Because the underlying values for these form controls are strings, using value.bind
doesn't work. If you're binding to an array of strings, value.bind
works just fine.
The checked.bind
attribute states that when a radio button (or checkbox) is checked, bind to the currentColor
property on the view model. However, because the binding takes place within the scope of the repeat.for
, it uses the $parent
property when referencing its parent view-model.
When you refresh the page, you can change the color of the header by selecting any of the four radio buttons. To use a drop-down list, add the following snippet:
<select value.bind="currentColor">
<option
repeat.for="color of headerColors"
model.bind="color">
${color.colorName}
</option>
</select>
When you refresh the page, the drop-down can now change the header color, and the radio buttons also change to match the current color selection and vice-versa.
Both native and custom events can also be bound using either the .trigger
or .delegate
binding expressions. The .trigger
binding expression attaches an event handler directly to the element that's called when the event fires. When the .delegate
binding expression is used, a single event handler is attached to the document that handles all events of the specified type. If the event
object is needed, it can be passed in the expression by using $event
.
To see event binding in action, add the following snippet to the end of binding-demo.js
:
triggerBoxStyle = {
width: "300px",
height: "300px",
border: "1px solid black",
'background-color': "#ccc",
'margin-top': "10px"
};
currentPosition = { x: 0, y: 0 };
mouseClicks = [];
captureMouseMove(event) {
this.currentPosition = {
x: event.clientX,
y: event.clientY
};
}
captureMouseClick() {
this.mouseClicks.push(this.currentPosition);
}
clearClicks() {
this.mouseClicks = [];
}
The first part of the snippet has nothing to do with event binding but is needed for the demo to style a div element. Aurelia allows styles to be constructed as objects. Notice the properties for background-color
and margin-top
. Because JavaScript doesn't allow dashes as part of a property name, it does allow strings. To access such a property in JavaScript, the [] notation must be used. For example:
triggerBoxStyle['background-color'] = "#FFF";
The remaining snippet sets up the objects and methods needed to track the mouse movements and button clicks that take place inside a <div>
element on the view. Add the following snippet after the <select>
element in binding-demo.htm:
<div style.bind="triggerBoxStyle"
mousemove.trigger =
"captureMouseMove($event)"
click.delegate="captureMouseClick()">
x: ${currentPosition.x},
y: ${currentPosition.y}
<ol>
<li show.bind="mouseClicks.length > 0">
<button type="button"
click.delegate="clearClicks()">
Clear
</button>
</li>
<li repeat.for="click of mouseClicks">
Click at (${click.x}, ${click.y})
</li>
</ol>
</div>
A lot of binding takes place inside this <div>
element and it starts with binding the element's style to the triggerBoxStyle
property. Because this is binding to an object, style.bind
can be used rather than using the custom css attribute alias as was done in a previous example. The next two binding expressions bind the div element's mouse move and click events. Notice that mousemove
is used and not onmousemove
. The “on” prefix isn't required. Because the mouse position is needed, the $event
object is passed to the handler. The next binding expression uses string interpolation to display the current mouse position inside the element.
Inside the <ol>
element is something new. Notice that the first list item element uses a binding of show.bind
. This type of expression can be used to show or hide an element if the expression is true
. In this example, the list element only appears when the mouseClick
array isn't empty. This type of expression toggles the visibility of an element within the DOM. If the element needs to be added or removed, if .bind
is used.
<li if.bind="mouseClicks.length > 0">...</li>
When this list element is visible, it provides a button bound to the clearClicks()
handler to clear the mouse click array. The last binding expression is another example of using repeat.for
to render the items within the mouse click array.
There are further subtleties to Aurelia's binding that are beyond the scope of this article, but I encourage you to explore it on your own.
HTTP Communication
You now know how to construct Aurelia components and set up navigation between them, but what about getting data in to and out of these components? Luckily, Aurelia has three options to cover this. The first two are Aurelia libraries, and the third option is whatever library you are currently using or wish to use. As mentioned earlier, Aurelia is a collection of collaborating libraries architected to play nice with third-party libraries. Everything in Aurelia is meant to be pluggable, and the end result allows developers to use only what is needed. This minimizes the bloat within applications. This also means that the two HTTP libraries mentioned earlier in the paragraph are not included in the core framework and need to be installed from the command line using jspm.
The first library option is aurelia-http-client
, which is an HttpClient based on XMLHttpRequest. All HTTP verbs are supported, along with JSONP and request cancellations.
The second library option is aurelia-fetch-client
, which is a simple client based on the Fetch standard. All HTTP verbs are supported and integrate with Service Workers, including Request
and Response
caching. Not all browsers support this standard, and this library currently doesn't have any polyfills. A third-party polyfill, such as the GitHub's Fetch polyfill, can be used.
For our hands-on example, the aurelia-http-client
library is used and the binding-demo
component is modified to load a local JSON file setting the list of colors for changing the header.
Install the aurelia-http-client
library in the project's root directory with this command:
jspm install aurelia-http-client
Libraries are imported into view-model classes. Add the following to the top of binding-demo.js
:
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-http-client';
The first import adds dependency injection support and the HttpClient library is imported next. Notice how, after the from
statement, the library name matches the name used for installation.
Dependency Injection makes it possible to pass the HttpClient to the class rather than requiring the class to create a reference to the library. The following shows how to inject a dependency into the BindindDemo
class:
@inject(HttpClient)
export class BindingDemo {
constructor (httpClient) {
this.httpClient = httpClient;
}
...
An ES2016 inject decorator is added to the top of the class declaration, and a class constructor with a single parameter is added after the class declaration. For every type injected into the class, there's a matching constructor parameter.
Although this might be alien, it's still JavaScript and not an Aurelia construct. SystemJS makes it possible to write ES6 JavaScript and takes care of transcompiling to ES5 JavaScript.
To use HttpClient, remove the headerColors
and currentColor
properties from binding-demo.js
and add the following method to the end of the class:
activate() {
return this.httpClient.get('data.json')
.then(data => {
this.headerColors = data.content;
this.currentColor = this.headerColors[0];
});
}
Activate()
is one of four optional methods in Aurelia's Screen Activation Lifecycle. Table 2 documents all four of the optional methods.
Aurelia's router enforces this lifecycle and provides hooks to view-models to control flow in to and out of components. The activate
method makes an HTTP call to get the data.json
file and returns a promise to the router. Because a promise is being returned, the router waits until the promise has completed before rendering the view. Notice the use of a lambda expression in the then
callback. This is more ES6 goodness from SystemJS.
Before running this code, add the data.json
file to the root folder. Listing 8 shows its details. Once it's added, refresh the page to see a new list of colors that match the data file.
Listing 8: data.json
[
{
"colorValue": "#000000",
"colorName": "BLACK"
},
{
"colorValue": "#FF0000",
"colorName": "RED"
},
{
"colorValue": "#00FF00",
"colorName": "GREEN"
},
{
"colorValue": "#0000FF",
"colorName": "BLUE"
},
{
"colorValue": "#4B0082",
"colorName": "INDIGO"
},
{
"colorValue": "#7FFF00",
"colorName": "CHARTREUSE"
},
{
"colorValue": "#FFA07A",
"colorName": "LIGHT SALMON"
}
]
Obviously, there's much more to HTTP communication than a simple GET in order to read a JSON file. The goal here is to demonstrate the base concept because all calls follow the same pattern: Make an asynchronous call to an HTTP endpoint, assign the data returned to one or more properties in the view-model, and bind to it in the view.
Conclusion and Going Further
That should be enough to get you started. Our goal for this article was to introduce you to the Aurelia framework and show you how simple and straightforward it is to begin using it to create your next JavaScript application. We've been using it on a current project and have found it to be a great addition to our development toolbox. There's documentation available on the Aurelia website (https://aurelia.io/), so go check it out. In the docs, you'll find additional information about how Aurelia handles dependency injection, how to create custom attributes, how to secure your applications, how to implement end-to-end testing, and much more.
Table 1: Route object properties
Route Property | Description |
route | Patten used to match a URL to a component. Can be a single pattern or an array of patterns. Patterns can be static or contain parameters like product/:sku |
name | The name used in code for generating URLs. |
moduleId | The path to the module. This will match the file name of component without the js extension. |
title | An optional title that will be used for the document's title or the link when included in the navigational model. |
nav | Determines if the route participates in the navigational model. Can be true or a number to indicate the order of the navigation. |
Table 2: Aurelia Screen Activation Lifecycle
Method | Description |
CanActivate | Implement to determine whether or not a view-model can be navigated to. Must return a Boolean value, promise for Boolean value, or navigation command. |
Activate | Implement for custom logic needed before a view-model is displayed. When a promise is returned, the router waits for the promise to complete before rendering the view. |
CanDeactivate | Implement to determine whether or not a view-model can be navigated away from. |
Deactivate | Implement for custom logic needed before navigation away from view-model. When a promise is returned, the router waits for the promise to complete before navigating away. |