Creating a Next Gen JavaScript Application with Aurelia
2015 brings with it the finalization of the ECMAScript 6 specification and with that the confidence to build modern, superior applications in JavaScript.
The current landscape of JavaScript frameworks is dominated by the recognizable giants AngularJS and React both of which are aiming in some way, shape or form, to incorporate new ES6 features into their paradigms.
There is however, another player that while new and relatively secretive, looks elegant in its use of modern JavaScript features. I’d like to take a moment to introduce you to Aurelia.
Aureli-who?
Aurelia is a next generation framework that leverages modern concepts like ES6, Web Components, and modularization to help you develop performant, futureproof applications.
Aurelia is the natural progression of Durandal, an AngularJS competitor built by Rob Eisenberg. Aurelia’s history involves a number of encounters with the AngularJS team over the years. It’s for this reason that many aspects of the framework might feel familiar to the AngularJS developers among you.
New Technologies
As I said, Aurelia is a “next generation” framework and as a consequence the tools it uses may be new to some of you. It runs on Node.js, and uses npm but it relies on a few cool new pieces of tech that we’ll look at briefly below:
Gulp
This one isn’t so new but it’s a core part of Aurelia’s setup. We’ll use Gulp to pipe all our files through various tasks to ensure our application is all wired up and ready to go.
ES6 Module Loader Polyfill
The ES6 module loader is a pollyfill for the System
dynamic module loader that was part of the original ES6 specification. The System
loader is in the process of being written into browser specifications but in the meantime this polyfill provides a futureproof solution that we can use today.
The loader allows us to dynamically load modules defined in the ES6 module syntax using the System.import
method:
System.import('mymodule').then(function(m) { ... });
In addition to loading ES6 modules, the loader allows to load other module syntaxes through the use of hooks.
SystemJS
With its slightly confusing name, SystemJS is essentially a collection of loader hooks for the ES6 module loader that enable us to load modules from npm, jspm, ES6 Modules and more. You can think of it as a feature rich module loader built on the future proof foundation of the ES6 Module Loader Polyfill.
jspm
jspm is a package manager, like npm, designed to be used with SystemJS. It allows us to install packages from various sources and exposes those to our app so we can easily import them with SystemJS.
Let’s Get Set up
I’m going to assume you’ve already installed Node.js, npm and Git, and that you’re familiar with the use of all of them.
We’ll start by cloning the Aurelia example application repository from GitHub
git clone https://github.com/aurelia/skeleton-navigation.git
At this point you might ask: “Why are we cloning their example app rather than starting our own from scratch?”
The reason is that Aurelia is still in an early stage, thus there’s no simple aurelia init
command yet that you can run to get your package.json
file and everything set up.
The repository we cloned acts as a good base for our app. It gives us a directory structure, a package manifest, some testing configuration and more. Hopefully one day there’ll be an installer of sorts or we’ll defer to generators like Yeoman the setup. Since we’re using the repository for its configuration and not for their example app itself, you can go ahead and delete the src/
directory, and the styles/styles.css
and index.html
files. We’ll create our own shortly.
We’ll need to install a few other things in order to install our dependencies and kick start our app:
Install gulp globally so that we have access to the gulp CLI:
npm install -g gulp
Then, install jspm globally for the same reason.
npm install -g jspm
Now open the CLI and move to your app’s root directory. Once done, run the command:
npm install
It’ll install our dependencies (from the package.json
file) that include among other things:
- Aurelia tools
- Gulp plugins
- Karma packages for testing
Once the process is completed, we’ll install our jspm packages as well using the command:
jspm install -y
This is the bit that actually installs the modules that include Aurelia.
Last but not least, let’s install Bootstrap with jspm:
jspm install bootstrap
It’s worth noting that the Aurelia library (contained within these modules) has a number of dependencies on its own, including SystemJS. These will all be installed through dependency management as a result of installing Aurelia itself. I wanted to highlight this point just in case you’re wondering how we have access to things like SystemJS later on despite not having listed it explicitly here in our dependencies.
Time to build an app
We’ve now got a host of tools to help us build our app. What we need next is an index.html
page:
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="jspm_packages/github/twbs/bootstrap@3.3.4/css/bootstrap.min.css">
<link rel="stylesheet" href="styles/styles.css">
</head>
<body aurelia-app>
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>
System.config({
"paths": {
"*": "dist/*.js"
}
});
System.import('aurelia-bootstrapper');
</script>
</body>
</html>
Let’s step through the contents of <body>
.
As I mentioned before, SystemJS allows us to use the System.import
method. In this code, we use it to import the aurelia-bootsrapper
module which kicks off our Aurelia app. We can reference aurelia-bootstrapper
by name thanks to the config.js
file that jspm built for us when we ran jspm install -y
. It maps the module name, to its versioned source. Pretty nifty stuff.
The System.config
bit sets up the paths for our modules, i.e. where to start looking for files.
Now, create the styles/style.css
file and add this code to it:
body { padding-top: 74px; }
You’ll notice that we’re including Bootstrap which we installed earlier. The version may have changed at the time you read this tutorial, so take note of which one jspm installed.
What does the aurelia-bootstrapper do?
The aurelia-bootstrapper
module will scan the index.html
file for an aurelia-app
attribute. If such attribute specifies a value, then the bootstrapper will load the view/module with that name; otherwise it’ll load a view and module called app.html
and app.js
(which are the defaults). The view will get loaded into the element that has the aurelia-app
attribute (in this case the <body>
tag). It’ll be wired up to the app.js
file.
Let’s create an app.js
and app.html
file in the src
directory to see this in action:
export class App {
constructor() {
this.name = "Brad";
}
}
<template>
Hello, my name is <strong>${name}</strong>
</template>
The first thing you’ll notice is the use of the new ES6 module syntax and the export
keyword. You’ll also notice the use of the new ES6 class syntax and abbreviated function signatures. Aurelia, thanks to SystemJS, comes with support for many exciting ES6 features straight out of the box.
Here we see that app.js
defines a class whose properties are exposed as variables for use in the app.html
file. This class is known as a view-model, since it’s a data structure that backs our view. We print out the variables in our template using ES6 string interpolation syntax.
As the last note, I want to highlight that all the templates in Aurelia are wrapped in a <template>
tag.
Viewing our application in a browser
To get the app up and running in a browser, all we need to do is execute the command:
gulp watch
That’ll do all the magic of compiling ES6, live reload, and so on. You should be able to see your app at http://localhost:9000/
. As we expected, we see the contents of our template rendered inside the <bodygt;
tag and we see the property interpolated into the template.
Our gulpfile
has already setup BrowserSync for us so the page will reload if you make any changes.
Time to build our app
In this section, we’ll build a naive Reddit client that has two pages: “Funny” and “Gifs”. We’ll fetch data for each page from Reddit’s API and display a list on each page.
When building any application with multiple pages, the core of the application is the router and Aurelia is no different. Let’s change our app.js
file, so that it becomes the core module of our app. It’ll be responsible for defining and configuring routing.
import {Router} from "aurelia-router";
export class App {
static inject() { return [Router]; }
constructor(router) {
this.router = router;
this.router.configure(config => {
config.title = "Reddit";
config.map([
{route: ["", "funny"], moduleId: "funny", nav: true, title: "Funny Subreddit"},
{route: "gifs", moduleId: "gifs", nav: true, title: "Gifs Subreddit"}
]);
});
}
}
So, what have we done here?
The first line (import {Router} from "aurelia_router"
) imports the router itself using ES6 module import syntax.
Then, in the App
class we have a static function called inject
. Those of you familiar with AngularJS and not only will already know about dependency injection. The inject
function is going to determine, via dependency injection, what parameters will be available in our constructor function. In this instance, a single parameter will be provided and that’s our router. You can see we’ve altered the constructor function to accept that new parameter.
Dependency injection is powerful because it allows the loose coupling of modules and hands the control flow up a level meaning we can swap out those dependencies during testing or later on when they’re updated.
Now that we have the router available in the constructor of our class, we can use it to set up the routes.
First and foremost we set the router as a property of the class itself with this.router = router;
. This is an Aurelia convention and is necessary for routing to work. Note that naming is important in this instance.
Secondly, we configure our routes by using the config
object provided to us in the callback of this.router.configure
. We set a title
property that will be used to set the title of our pages. We also pass a list of route definitions to the config.map
function.
Each route definition has the following pattern:
{
route: ["", "foo"], // Activate this route by default or when on /foo
moduleId: "foo", // When active, load foo.js and foo.html (module)
nav: true, // Add this route to the list of navigable routes (used for building UI)
title: "Foo" // Used in the creation of a pages title
}
So, in our instance we’ve got two pages that we can visit at /#/funny
and /#/gifs
, with /#/funny
acting as our default page thanks to the ["", "funny"]
list of two route patterns.
We’ll also need to update app.html
to act as our app’s layout file.
<template>
<a href="/#/funny">Funny</a>
<a href="/#/gifs">Gifs</a>
<router-view>
</router-view>
</template>
Can you see the <router-view></router-view>
custom element? This is another built-in piece of Aurelia’s features. You can think of it like an AngularJS directive or just a web component. The view associated with the current route will automatically be loaded into this element.
Next, we’ll need to define the two modules: funny
and gifs
.
Writing our page modules
The “Funny” module
We’ll start with funny
and then copy it over as a basis for gifs
.
Create a /src/funny.js
file with the following content:
import {HttpClient} from 'aurelia-http-client';
export class Funny {
// Dependency inject the HttpClient
static inject() { return [HttpClient]; }
constructor(http) {
this.http = http; // Assign the http client for use later
this.posts = [];
this.subreddit_url = "http://reddit.com/r/funny.json";
}
loadPosts() {
// Aurelia's http client provides us with a jsonp method for
// getting around CORS issues. The second param is the callback
// name which reddit requires to be "jsonp"
return this.http.jsonp(this.subreddit_url, "jsonp").then(r => {
// Assign the list of posts from the json response from reddit
this.posts = r.response.data.children;
});
}
// This is called once when the route activates
activate() {
return this.loadPosts();
}
}
Also create /src/funny.html
as follows:
<template>
<ul class="list-group">
<li class="list-group-item" repeat.for="p of posts">
<img src.bind="p.data.thumbnail" />
<a href="http://reddit.com${p.data.permalink}">
${p.data.title}
</a>
</li>
</ul>
</template>
The “Gifs” module
Let’s simply copy our funny.js
and funny.html
to src/gifs.js
and src/gifs.html
respectively. We’ll need to tweak the contents of gifs.js
a little.
import {HttpClient} from 'aurelia-http-client';
export class Gifs {
static inject() { return [HttpClient]; }
constructor(http) {
this.http = http;
this.posts = [];
this.subreddit_url = "http://reddit.com/r/gifs.json";
}
loadPosts() {
return this.http.jsonp(this.subreddit_url, "jsonp").then(r => {
this.posts = r.response.data.children;
});
}
activate() {
return this.loadPosts();
}
}
Now you should be able to visit localhost:9000/#/gifs
to see a list of gif posts and their links.
Improvements to our layout
We can make a couple of improvements to our layout template using Aurelia’s router.
Remember the nav:true
property we set in our route config earlier? What it does is to add a route to a list that we can iterate over in our view in order to build dynamic navigation. Let’s do that now.
Update the contents of app.html
as follows:
<template>
<div class="container">
<ul class="nav navbar-nav navbar-fixed-top navbar-inverse">
<li repeat.for="navItem of router.navigation" class="${navItem.isActive ? 'active' : ''}">
<a href.bind="navItem.href">
${navItem.title}
</a>
</li>
</ul>
<router-view></router-view>
</div>
</template>
Conclusion
Well there you have it! Your first Aurelia application. I’m pretty excited about the future of Aurelia as I think it’s clean and straightforward. Moreover, by using ES6 it keeps everything in reusable, extendable modules. In future tutorials, I’ll look at how we can abstract the duplication between the Gifs and Funny modules, as well as some other improvements and additions to our Reddit client. I’d love to know how your first attempt at app development with Aurelia goes!
The complete application that we’ve built during this article can be found here