Aurelia Tips & Tricks

Last updated: July 3, 2018

If you are new to Aurelia or perhaps already building something with it, here are some tips and tricks I have learned over the last two years which have helped me write well-performing Aurelia applications.

Keep in mind that Aurelia is like any other framework or library and that it is very easy to write poor performing applications if you’re not careful. Never assume that a framework will stop you from writing bad code.

If vs Show Bindings

In Aurelia there are two attributes: if and show which are similar to one another, but have some fundamental differences. The if binding will add or remove elements from the DOM, whereas the show binding just applies a conditional display:none to whatever element the attribute is used on.

Understandably, for some it is confusing. Do you use if or do you use show? My rule of thumb is defined by how often I am going to show or hide something in my application.

Am I going to show or hide an element in the page regularly or am I conditionally going to show or hide something sporadically or only just once?

The upside of the if binding is when the bound value is false, the elements in the DOM will be removed (freeing up DOM space and bindings will be removed). The downside being Aurelia needs to set up a new controller instance and bindings (which isn’t that big of a deal).

I am a huge fan of a light DOM, so I use if.bind extensively because a large DOM is a lot slower than the time it takes for Aurelia to setup controller instances and bindings. Both have a purpose, it is important to use what works for you.

I do find if.bind works well for me in most cases, no point having elements around if they’re not being used (in my opinion).

Avoid dirty-checking at all costs

If you’re not sure what dirty-checking is, essentially it’s a timer with a 120-millisecond timeout value that repeatedly loops and checks for changes. You definitely do not want dirty-checking in your application (unless you absolutely need it for some edge case).

If you define a getter in your view-model which accesses one or more values in your class and you do not supply the computedFrom decorator, it will be dirty-checked.

An example of dirty-checking can be seen below:

export class MyClass {
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    }
}

<template>
    <h1>${fullName}</h1>
</template>

Internally, Aurelia is going to dirty-check this using a setInterval to constantly poll if the value changes, this is bad. Fortunately, using computedFrom we can make this an optimised getter which only reacts when one or more dependencies change.

The below example does not use a timer to poll for changes, it makes the getter reactive.

import {computedFrom} from 'aurelia-framework';

export class MyClass {
    @computedFrom('firstName', 'lastName')
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    }
}

<template>
    <h1>${fullName}</h1>
</template>

A note on computedFrom: the this part is implied, so when supply one or more computed values to computedFrom you are automatically referencing the current class and do not need to supply this.

A note on computed arrays

the computedFrom decorator is great for telling Aurelia about your getter dependencies, but it doesn’t work for all values. One such situation is arrays, an array cannot be directly supplied as a getter computed value because it won’t update.

If you’re looking to force a getter to update when an array changes, computed based on its length. When the size of the array changes (items are added or removed) the getter will fire.

import {computedFrom} from 'aurelia-framework';

export class MyClass {
    @computedFrom('arr.length')
    get firstValue() {
        return `${this.arr[0]}`;
    }
}

<template>
    <h1>${firstValue}</h1>
</template>

Think big by thinking small

If you come from an MVC background, then you’ve heard the expression, “fat model, skinny controller” and other countless expressions around MVC architecture which have sort-of been carried over into the front-end development jungle.

I prefer fat nothing in my Aurelia applications (or web applications full-stop). If your view-models and/or views are massive, then you have a code architecture problem which is easily solved by reusable components.

A large view-model or view is eventually going to be your undoing (besides the annoying Git conflicts you will encounter working in a team on large files). Large files which do too much are counterproductive and unnecessarily complicated.

If you want to ensure your application is easy to build upon and make it easier for the browser, build tools and test runners to work with your code: break things up into reusable components.

I highly recommend approaching the architecture of your Aurelia applications using the Single responsibility principle.

Use view-cache

This isn’t documented very well, but Aurelia provides a view-cache which can allow you to cache heavy views. This means if you have quite a complex view and you navigate away, but come back to it later Aurelia will use the view-cache and update all of the appropriate bindings.

The benefits are obvious: the whole view doesn’t need to be recreated when you navigate back to it. Just throw view-cache onto the main opening <template view-cache="*"> tag, which also accepts a cache size value.

If you supply an asterisk * to view-cache it will cache all instances or if you supply a numeric value, cache the number supplied.

Use compose when nothing else fits

Aurelia provides a <compose> custom element which allows you to dynamically compose from within your application. You can dynamically compose view-models, views and pass in data as well.

As great as <compose> is, I try and avoid it wherever I possibly can. It’s not that I think <compose> is bad, it’s the fact that <compose> is an abstraction and where possibly, I try and avoid abstractions in my applications.

I have experienced instances where <compose> has made debugging more difficult than straight-up view-models and components.

Assess your needs and if <compose> is the only option, then by all means use it.

Use Binding Behaviors

Out-of-the-box, Aurelia ships with some great binding behaviours which allow you to make bindings in your applications more efficient and performant.

A binding behaviour in your view templates is denoted by an ampersand “&” and much like value converters, they can be chained and have parameters which accept values.

throttle

A rate-limiting throttling behaviour which allows you to throttle the rate in which the view-model is notified of a change in a two-way binding situation. By default the throttle value is 200ms.

debounce

Very similar to the throttle binding behaviour, the debounce behaviour will prevent a binding from being updated until the debounce interval completes after no changes have been made.

updateTrigger

Allows you to specify which input events signal to Aurelia’s binding system that a change has been made. By default, the events that trigger a change are change and input.

signal

Allows you to tell Aurelia’s binding system to refresh a binding. It works like an event listener on a binding and when you fire an update signal, it refreshes the binding which works similarly to how a first-page load does.

oneTime

Use the oneTime binding behaviour as much as possible. It allows you to tell Aurelia’s binding system to display a binding value and then forget it. A one-time binding will not update if the value changes, which can lead to great application performance.

If you would like to see examples of binding behaviours and how to work with them, Aurelia core team member has a great write-up on working with binding behaviours here which I recommend you also read.

Batch DOM changes using the Task Queue

The Aurelia Task Queue is a powerful module that allows you to sort of hook into the browsers event loop which allows you to perform DOM actions in an efficient way.

While slow Javascript code can definitely affect performance, it is a known fact that the DOM is the bottleneck of any web application, which is why libraries like React and Vue leverage a virtual DOM. Working with the DOM inefficiently can result in poor browser performance.

By queuing up a microtask, we can batch changes in the page resulting in less reflow and repaints.

import {TaskQueue} from 'aurelia-task-queue'; 

export class MyViewModel {
    static inject = [TaskQueue];

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

    attached() {
        this.makeSomeChanges();
    }

    makeSomeChanges() {
        this.taskQueue.queueMicroTask(() => {
            const header = document.getElementById('header');

            // Dynamically set the width and height of an element with ID "header"
            // We are batching DOM changes, so we only get on repaint/reflow
            header.style.height = '250px';
            header.style.width = '500px';
        });
    }
}

Be smart about event handling

In Aurelia, there are two ways to create event listeners. You can create an event listener the Javascript way using addEventListener or you can also bind to events from within your views with one such example being click.delegate="myCallback()".

Where possible, you should use Aurelia’s templating for binding to events. The biggest advantage being Aurelia cleans up after itself and removes events when they’re no longer being used.

This is not to say that addEventListener is completely redundant, but for eventing around elements in your page, you can and should use Aurelia’s provided way of working with native events.

You’ll have the piece of mind that events are properly being unbound when views are being destroyed, it’s like programming languages that automatically manage memory for you like PHP.

Don’t abuse the Event Aggregator

Aurelia comes with a great Event Aggregator module which allows you to publish and subscribe to events from within your application. The routing module also uses the Event Aggregator, firing off various events at different stages of the routing process.

As great as the Event Aggregator is, I have seen it abused quite a lot. I have done a bit of Aurelia consulting and in most instances, the Event Aggregator is being used for things it shouldn’t be used for.

The biggest issue you will encounter with the Event Aggregator is that it is hard to debug. This rings true for most custom eventing, not just Aurelia’s module.

So, when are you supposed to use the Event Aggregator then?

Do’s

  • Use the Event Aggregator to notify parts of your application about specific actions; user added, recorded deleted, a user logged in, a user logged out, etc.
  • Use the Event Aggregator to react to specific actions; a modal opening or closing, a user is deleted or if the user logs out clearing out a token in localStorage, etc.

Do nots

  • Don’t use the Event Aggregator for passing data between routes
  • Don’t use the Event Aggregator for passing data between components
  • Never treat the Event Aggregator as a crucial dependency (creating components that rely on specific events and values)

Passing data between routes

A common scenario I have witnessed in the community is wanting to route from Route A to route B and pass some data to the route you are navigating too.

I’ve seen developers pass data using the Event Aggregator (see above, don’t do that), use localStorage (also, don’t do that) and the even crazier: storing it on the route object itself (seriously, don’t do that).

The way that I always recommend passing data between routes and components is by using a singleton class. If you come from an Angular background, then you might know this as a service class.

Assuming you have two routes: Route A and Route B, and in Route A the user can enter their name and then be navigated to RouteB which will display their name.

I would achieve this in the following way:

    import {AppService} from './app-service';

    export class RouteA {
        static inject = [AppService];

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

    <template>
        <h1>Welcome: Route A</h1>
        <label>Please enter your name: <input type="text" value.bind="appService.name"></label>
    </template>

    import {AppService} from './app-service';

    export class RouteB {
        static inject = [AppService];

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

    <template>
        <h1>Hello, ${appService.name}</h1>
        Welcome to RouteB. The name you provided on the previous page is displayed above.
    </template>

    // Assuming this file is saved as app-service.js alongside the other route view-models
    export class AppService {
        constructor() {
            this.name = '';
        }
    }

Or better still, use a state management solution if your application state is complex and requires a much more manageable solution. The Aurelia Store is my favourite and recommended state management solution for Aurelia applications.

Purchase Aurelia for Real World Applications over at Leanpub now

 

Dwayne

 

Leave a Reply

Your email address will not be published. Required fields are marked *