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.