Observing Arrays in Aurelia
Photo by Stanislav Kondratiev on Unsplash
If you're struggling to observe array data changes in Aurelia, check out these code snippets.
If you want to observe changes to an array in Aurelia, you can't simply use the
@observable
decorator as it won't recognize mutations to the array. Instead,
you need to use the collectionObserver
in the BindingEngine
.
Example:
1import {BindingEngine, inject} from 'aurelia-framework'23@inject(BindingEngine)4export class App {5 constructor(bindingEngine) {6 this.bindingEngine = bindingEngine7 this.data = []8 }910 attached() {11 this.subscription = this.bindingEngine12 .collectionObserver(this.data)13 .subscribe(this.dataChanged)14 }1516 detached() {17 this.subscription.dispose()18 }1920 dataChanged(splices) {21 console.debug('dataChanged', splices)22 }23}
There are some gotchas that you should be aware of though...
In the example above, if you were to replace the value of this.data
completely, then your collectionObserver
will not work, unless you subscribe
again.
If you are always going to replace your array data, then the @observable
would
work at this point.
As an example, check out this gist.run.
Here is the code for reference:
1import {BindingEngine, inject, observable} from 'aurelia-framework'23@inject(BindingEngine)4export class App {5 @observable data2 = []67 constructor(bindingEngine) {8 this.bindingEngine = bindingEngine9 this.data = []10 this.log = []11 }1213 attached() {14 this.observeData()15 }1617 observeData() {18 if (this.subscription) {19 this.subscription.dispose()20 }21 this.subscription = this.bindingEngine22 .collectionObserver(this.data)23 .subscribe(splices => {24 this.dataChanged(splices)25 })26 }2728 detached() {29 if (this.subscription) {30 this.subscription.dispose()31 }32 }3334 dataChanged(splices) {35 console.debug('dataChanged', splices)36 this.log.unshift('data changed ' + new Date())37 }3839 data2Changed(newVal) {40 console.debug('data2Changed', newVal)41 this.log.unshift('data2 changed ' + new Date())42 }4344 addData() {45 this.data.push(new Date())46 this.data2.push(new Date())47 }4849 popData() {50 this.data.pop()51 this.data2.pop()52 }5354 spliceData() {55 const rand = Math.floor(Math.random() * this.data.length)56 this.data.splice(rand, 1, 'spliced')57 this.data2.splice(rand, 1, 'spliced')58 }5960 replaceData() {61 this.data = ['replaced', 'data', 'completely']62 this.data2 = ['replaced', 'data', 'completely']63 }64}
You will notice that anytime we are modifying the array of data (if you click
the Add Data, Pop Data, or Splice Data buttons) the dataChanged()
function is
triggered. The observable data2
variable is not. If you press the Replace Data
button you will see that the data2Changed()
function is triggered, and the
dataChange()
function is no longer triggered on the data
array changing.
So you may have to come up with a combination of solutions if you want to observe both the array being replaced and the array being modified. Something like this:
1import {BindingEngine, inject, observable} from 'aurelia-framework'23@inject(BindingEngine)4export class App {5 @observable data = []67 constructor(bindingEngine) {8 this.bindingEngine = bindingEngine9 this.log = []10 }1112 attached() {13 this.observeData()14 }1516 observeData() {17 if (this.subscription) {18 this.subscription.dispose()19 }20 this.subscription = this.bindingEngine21 .collectionObserver(this.data)22 .subscribe(splices => {23 this.dataModified(splices)24 })25 }2627 detached() {28 if (this.subscription) {29 this.subscription.dispose()30 }31 }3233 dataChanged(newVal) {34 this.log.unshift('data changed ' + new Date())35 this.observeData()36 }3738 dataModified(splices) {39 this.log.unshift('data modified ' + new Date())40 }41}