Preventing Dirty Checking In Your Aurelia Applications Using computedFrom


#1

The concept of dirty-checking harks back to the early days of Javascript frameworks, when limitations of browser API’s meant vendors would implement hacks into their frameworks to detect when a change had been made to an object or collection (usually 120 milliseconds).

The easiest way to describe dirty-checking is a timer continually runs (even if nothing is happening) every 120ms or so, looking for changes to whatever is being dirty-checked. In small doses, dirty-checking is harmless (we put up with it in Angular 1.x for ages), but in medium to large sized applications it can pose a performance problem.

It might be 2017, but dirty-checking is not completely dead just yet.

What causes dirty checking?

In Aurelia specifically, you’ll encounter dirty-checking if you attempt to bind to a getter defined inside of a view-model that references other properties.

export class App {
	constructor() {
		this.firstName = 'Dwayne';
		this.lastName = 'Charrington';
	}
	
	get fullName() {
        console.log('Dirty!!');
		return `${this.firstName} ${this.lastName}`;
	}
}
<template>
    ${fullName}
</template>

Our getter is being displayed in our HTML view, but because Aurelia can’t work out what the dependencies are for our getter it results in constant polling and this is something that can easily be avoided in almost every scenario.

If you run the above code and open up your browser developer tools, you should see the text, “Dirty!!” getting repeatedly spammed. Now imagine that was a real value in a real application, not good for performance in the long run.

computedFrom

Fortunately, Aurelia has had the ability to allow getters to declare their dependencies in the form of the computedFrom decorator. When creating a computed, your application becomes reactive to changes instead of hyperactive (where things are constantly polled).

import { computedFrom } from 'aurelia-framework';
 
export class App {
	constructor() {
		this.firstName = 'Dwayne';
		this.lastName = 'Charrington';
	}

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

The computedFrom decorator can accept one or multiple arguments specifying a getter dependency. In our example we specify the first and last name values as dependencies, whenever they change our getter will re-run and update its value.

Implied this

Worth mentioning is: you’ll also notice we don’t supply this to the computedFrom decorator, that is because the context for the computed is presumed to be the current view-model and as such, you don’t need to provide this to the decorator.

computedFrom with paths

In our example we are observing class properties, but what if our getter references a value from within an injected singleton class or object?

The computedFrom decorator allows you to specify object paths for dependencies as well. So, we’ll rewrite our above example to reference a fictional singleton service class that we inject into our current view-model.

Our first and last name properties exist on this injected singleton now, so we reference the singleton and then the property name from within the computed.

import { computedFrom } from 'aurelia-framework';
import { MyService } from './services/my-service';

export class App {
    static inject = [MyService];

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

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

Conclusion

The use of getters without computed properties realistically the only kind of dirty-checking you’ll encounter. Being mindful of this aspect during development will save you headaches in the long run.


#2

Just to add onto this, Eric Lieben has a nice way to preserve typings if you use typescript with a typed version of computedFrom.


#3

On the same idea as @mttmccb mentioned… the ts-nameof module works very well with this attribute in typescript. https://www.npmjs.com/package/ts-nameof


#4

Is there any way of tracking down all properties that are being dirty checked? I’ve tried the method suggested here; https://blog.rsuter.com/aurelia-js-detect-dirty-checked-properties-during-development/, but I can’t get it to work… What I want is to log out all properties that are being dirty checked so that I can fix them with the @computedFrom attribute :slight_smile:


#5

If I’ve wanted to do this before, I just put a breakpoint in a known dirty checked property and then looked up the call stack until I find the list of dirty checked properties its iterating over.

Another option is to use aurelia-computed with debug output enabled which lists all the bindings where its fallen back to dirty checking. aurelia-computed can also negate the need to add computedFrom on every property.