Observing Arrays in Aurelia
Photo by Stanislav Kondratiev on Unsplash
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:
import {BindingEngine, inject} from 'aurelia-framework'
@inject(BindingEngine)
export class App {
constructor(bindingEngine) {
this.bindingEngine = bindingEngine
this.data = []
}
attached() {
this.subscription = this.bindingEngine
.collectionObserver(this.data)
.subscribe(this.dataChanged)
}
detached() {
this.subscription.dispose()
}
dataChanged(splices) {
console.debug('dataChanged', splices)
}
}
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:
import {BindingEngine, inject, observable} from 'aurelia-framework'
@inject(BindingEngine)
export class App {
@observable data2 = []
constructor(bindingEngine) {
this.bindingEngine = bindingEngine
this.data = []
this.log = []
}
attached() {
this.observeData()
}
observeData() {
if (this.subscription) {
this.subscription.dispose()
}
this.subscription = this.bindingEngine
.collectionObserver(this.data)
.subscribe(splices => {
this.dataChanged(splices)
})
}
detached() {
if (this.subscription) {
this.subscription.dispose()
}
}
dataChanged(splices) {
console.debug('dataChanged', splices)
this.log.unshift('data changed ' + new Date())
}
data2Changed(newVal) {
console.debug('data2Changed', newVal)
this.log.unshift('data2 changed ' + new Date())
}
addData() {
this.data.push(new Date())
this.data2.push(new Date())
}
popData() {
this.data.pop()
this.data2.pop()
}
spliceData() {
const rand = Math.floor(Math.random() * this.data.length)
this.data.splice(rand, 1, 'spliced')
this.data2.splice(rand, 1, 'spliced')
}
replaceData() {
this.data = ['replaced', 'data', 'completely']
this.data2 = ['replaced', 'data', 'completely']
}
}
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:
import {BindingEngine, inject, observable} from 'aurelia-framework'
@inject(BindingEngine)
export class App {
@observable data = []
constructor(bindingEngine) {
this.bindingEngine = bindingEngine
this.log = []
}
attached() {
this.observeData()
}
observeData() {
if (this.subscription) {
this.subscription.dispose()
}
this.subscription = this.bindingEngine
.collectionObserver(this.data)
.subscribe(splices => {
this.dataModified(splices)
})
}
detached() {
if (this.subscription) {
this.subscription.dispose()
}
}
dataChanged(newVal) {
this.log.unshift('data changed ' + new Date())
this.observeData()
}
dataModified(splices) {
this.log.unshift('data modified ' + new Date())
}
}