mikeesouth
mikeesouth

Reputation: 1640

Update Aurelia binding when properties change

I'm using Aurelia with TypeScript. I'm trying to bind an object to the view and I want the view to update when I change a property of my object. I know that Aurelia only watches the object and some array manipulations (push, splice etc). I also know that there are some binding helpers such as @computedFrom and using the BindingEngine but I still can't find the best approach when using value converters.

In my example I have class in TypeScript, e.g. "class Car". Then I bind multiple car objects to the view, e.g. ${car1}, ${car2} etc. I add a value converter to present the car, e.g. ${car1 | carPresenter}. This displays the information like this: "A blue car with full tank and three passengers". If I change a car property, e.g. "car1.passengers++" then I want the ${car1 | carPresenter} to update.

Maybe a value converter is the wrong approach? Please advise on better methods if that's the case. I want to present complex objects by showing some of it's properties but not necessarily all of them. And those presentations should update when the underlying properties change.

I have created a gist with a simplified example that illustrates the problem: https://gist.run/?id=06287de5eb179b32fa0b134eb59ebc68

Upvotes: 3

Views: 3634

Answers (2)

AnorZaken
AnorZaken

Reputation: 2134

If you want to bind and react to inner changes, here's a little trick of mine...

  1. Make sure the object in question has a class (class Car in this case)
  2. Add an get observe() getter to the class that return { self: this };
  3. Add @computedFrom(... with every property of the class.
  4. In your html, where you want this binding, bind to car.observe.self
  • P.S. Make sure every property of the class has a default value OR a decorator.
    Otherwise computedFrom won't have anywhere to "latch on".
    See this SO answer for a better explanation: Aurelia's computedFrom with an object

So in your case the Car class should have a getter that looks like this:

@computedFrom('color', 'gasLevel', 'passengers')
get observe() {
  return { self: this };
}

And your html binding would look like this:

<strong>car: </strong>${car.observe.self | carPresenter}

Quick, simple, readable, and gets the job done (and supports inner observation nestling).


Full code:

app.js

import { Car } from './car';

export class App {
  car = new Car({
    color: 'blue',
    gasLevel: 'full',
    passengers: 1
  });
  
  attached() {
    this.increasePassengers();
  }
  
  increasePassengers() {
    this.car.passengers++;
    setTimeout(() => { this.increasePassengers(); }, 1000);
  }
}

car.js

import { computedFrom } from 'aurelia-framework';

export class Car {
  color = undefined;
  gasLevel = undefined;
  passengers = undefined;
  
  constructor(data) {
    const values = data || {};
    const fieldNames = Object.getOwnPropertyNames(values);
    for (let field of fieldNames) {
      if (field in this) {
        this[field] = values[field];
      }
    }
  }
  
  @computedFrom('color', 'gasLevel', 'passengers')
  get observe() {
    return { self: this };
  }
}

Q: Why not simply get self(): { return this; }
A: Aurelia is too smart for that; it sees that the same reference was returned and ignores it.

Upvotes: 0

Marton Sagi
Marton Sagi

Reputation: 1257

There is an additional binding decorator you can leverage: @observable [related docs].

More info: Working With Aurelia @observable (Dwayne's blog is an extremely useful resource for learning Aurelia).

Gist demo: https://gist.run/?id=c359860951717457e630e3fde1a4d6aa

In this demo, the Car object has its own class defined, where all necessary properties have an @observable decorator. Value converter has been replaced by description getter method.

Car class

import { observable } from 'aurelia-framework';

export class Car {
    @observable
    color;

    @observable
    gasLevel;

    @observable
    passengers;

    constructor(data) {
        // ...
    }

    get description() {
        return `A ${this.color} car with ${this.gasLevel} tank and ${this.passengers} passengers`;
    }
}

Upvotes: 3

Related Questions