1. The Fundamentals of Aurelia

Before we journey into Aurelia land and start building things, we need to talk about a few basic fundamentals that are key to almost every Aurelia application you will write. The theory before the practical.

Thankfully Rob Eisenberg and his carefully assembled team had a vision to abide by official specifications where possible, which has resulted in a framework with very little to understand. The key things you need to understand are just basic modern Javascript and HTML. However, there are conventions and ways things must be when you build an application.

The first thing you need to be aware of is Aurelia works on the premise of both views and viewmodels. If you come from a MVVM/MVC/MVwhatever background, then you will already be familiar with the concept of a view and viewmodel.

Most of what you end up doing in Aurelia will at the very least in most cases consist of a viewmodel. Sometimes you might have a view, sometimes you might only have a view and no viewmodel. There are no strict rules how things must be done, but there are certain constraints in how you do things, for your own sanity mostly.

Structure is important, so Aurelia’s modular component-first approach is something you will really enjoy if you come from a framework or library that has no structure or promotes a really convoluted way of working.

The CLI (which you’ll be using shortly) as well as the provided starter skeletons all assume your code will live inside of a directory called src you can change this, but we aren’t going to change it as src makes perfect sense, it’s just considered a best practice.

View

A view in Aurelia is literally just HTML that is contained inside of mandatory opening and closing <template></template> tags. This is a tag that was introduced into HTML5 which you can read more up on here. Although to be quite honest, there isn’t really anything to learn.

Aurelia takes the contents of your view, strips out the <template> tags and then gets to work bringing it into the context of your application, setting up your bindings, parsing use of control syntax and more.

Viewmodel

Keeping inline with the theme of abiding by official specifications, you write your Aurelia viewmodels as ES2015 classes: which we learned about in chapter two. If you skipped this chapter/section, go back and read it if you are not familiar, you are going to be using classes a lot.

Dependency injection (DI)

We are not going to go into too much detail about dependency injection as there is an entire chapter on it further on in the book. However, underpinning Aurelia is a powerful dependency injection module that is responsible for managing your modules.

In a lot of cases, you are going to be working primarily with the @inject decorator or explicit static inject property on the class. Later on we will get into specifics about; singletons, transients, resolvers and more.

Injecting dependencies: method one

This is my prefered method for injecting dependencies. Using the @inject decorator, we can inject classes/modules into our current class.

import {inject} from 'aurelia-framework';
import {EventAggregator} from 'aurelia-event-aggregator';

@inject(EventAggregator)
export class MyViewmodel {
  constructor(ea) {
    this.ea = ea;
  }
}

Pretty standard here. We are importing the inject decorator from the aurelia-framework module which is essentially a function. Then we import the event aggregator and tell @inject we want to register it with our class.

Then on the constructor method, we access our injected dependencies in the order they were defined. So event aggregator being the only injected dependency and the first supplied parameter, means it will be first on the constructor (as shown above).

Injecting dependencies: method two
import {EventAggregator} from 'aurelia-event-aggregator';

export class MyViewmodel {
  static inject = [EventAggregator];

  constructor(ea) {
    this.ea = ea;
  }
}

Very similar to the above example using the decorator, except we didn’t have to import anything. All we did was define a static class property called inject and assigned it an array of dependencies. Once again, you can have a comma separated list of dependencies, with the order being important.

The component lifecycle

In Aurelia each component goes through an event lifecycle where a series of callback functions are called at different times depending what is happening. This applies to not only Aurelia’s internal classes, but any component that you create such as a custom element or even just a generic viewmodel goes through this process.

constructor

The class constructor for your viewmodel. This is where you tell your viewmodel about any injected dependencies. The injected dependencies are parameters on your constructor function.

created(owningView: View, myView: View)

This method is called immediately after both the view and viewmodel has been created. We have direct access to the view instance via the view parameter passed through to this function.

bind(bindingContext: Object, overrideContext: Object)

This method is called after the databinding engine has bound to the view. The binding context supplied to this function is the instance that the view is databound to.

unbind

The inverse of bind. When the databinding engine unbinds the view, this method is called.

attached

In standard Javascript you are probably familiar with DOMContentLoaded or if you use jQuery, then $(document).ready() which is a callback to say the DOM is now ready for you to interact with. This method is called when the view has been attached to the DOM. Inside of this method is where you will mutate DOM elements, attach DOM events and instantiate jQuery plugins (which we will get too later on).

detached

This is the inverse of the attached() method. When the view is detached from the DOM, this callback hook is called. Perfect for cleaning up any events you might have registered in the DOM and deregistering jQuery plugins.

Really when it comes down to a real world application, you will find yourself working with only a few of the above mentioned callback lifecycle hook methods. However, it is convenient to know when each function is called and what order they are called in.

The official Aurelia documentation has a section on the component lifecycle you can use for reference here as well.

In the chapter on routing, you will also learn about the screen activation lifecycle which works similarly to the component lifecycle, however only strictly applies to viewmodels instantiated by the router.

Conventions

By default Aurelia makes some assumptions about how things should work, unless you tell it otherwise. These conventions allow you to write code with out needing to configure anything upfront. Some developers are not a fan of magic in frameworks and rather explicitly defining everything upfront, which is why Aurelia gives you the choice of both.

Viewmodel/View Pairs

In many cases, Aurelia makes the assumption when you have a viewmodel, you also have a view of the same name, just with a .html file extension. The situations in which Aurelia assumes you have a matching view are:

  • Routed viewmodels: When defining a route that points to a viewmodel, by default Aurelia will also expect a view file of the same name. If you route to src/welcome.js, Aurelia will look for a file src/welcome.html in the same location as the viewmodel.
  • Custom elements: When creating a custom element, it is assumed just like routed viewmodels, a view template of the same name and .html extension exists.
  • Dynamic composition with <compose>: When dynamically composing a viewmodel, if you do not specify a view and the viewmodel being composed does not specify a particular view or no view at all, a matching .html file will attempt to be loaded.

If for whatever reason your viewmodel has no view (on purpose) using the @noView class decorator you can tell Aurelia that it shouldn’t try looking for a view template.

import {noView} from 'aurelia-framework';

@noView()
export class MyViewModel {
    
}

Overriding view/viewmodel conventions

If you wanted to specify a view be loaded from somewhere different or an entirely different name, there are two ways you can do this.

Using @useView(viewString):

import {useView} from 'aurelia-framework';

@useView('somelocation/my-view.html')
export class MyViewModel {
    
}

Using getViewStrategy():

export class MyViewModel {
    // This method is fired upon viewmodel instantiation
    getViewStrategy() {
        return 'somelocation/my-view.html';
    }
}