We've looked at creating Observable properties, which let us watch for changes in our Application State. But how do our Components track those changes?
If you look at the UserView.render
method, we simply read from our User
. Cascade tracked those changes for us automatically using Computed properties. For our Components, this is done behind the scenes. But we can use them in our Application State just as easily.
Computed Properties
Cascade provides Computed properties for objects, which use a getter function to produce a value. However, any Observable properties used in this function, will automatically produce subscriptions.
For simplicity, we can also use the @observable
decorator, except in front of a getter function.
@observable get property(): type {
return this.value;
}
For our User
let's add a fullName
Computed property. Add this right under the firstName
and lastName
properties.
@observable get fullName() {
return this.firstName + ' ' + this.lastName;
}
There are a couple things to note:
- We simply used the values in order to subscribe to them automatically.
- We can use as many values as we want.
- Whatever we return from this method will be the value of the property.
Subscribing Directly to Observables
There are two main ways of subscribing to Observable properties. We may either subscribe directly, or through a Computed property, which we will examine next.
Cascade.subscribe<T>(obj: any, property: string, subscriberFunction: ISubscriberFunction<T>);
The subscriberFunction
will be called any time the value of the property changes. Keep in mind, simply storing an identical value to the property is not a change, and so there will be no notification.
Hiding Reads
Any time a Computed property references another Observable property, Cascade will automatically create a subscription. However, in some cases you may want to read, but not product a subscription. In this case, read the Observable with:
Cascade.peek(obj: any, property: string): any;
Update Batching
Cascade attempts to reduce updates to Computed properties whenever possible. For example, lets say we have a some Application State with multiple Observable properties and a Computed property which references them. Now let's say we change all of the Observable properties. Cascade will only update the Computed once.
class ViewModel {
@observable a: number = 0;
@observable b: number = 0;
@observable c: number = 0;
@computed get abc(): number {
return this.a + this.b + this.c;
}
}
// Create our Application State
let viewModel = new ViewModel();
// Subscribe to our Computed
Cascade.subscribe(viewModel, 'abc', (value) => {
console.log(viewModel.abc);
});
// Update our values
viewModel.a = 1;
viewModel.b = 2;
viewModel.c = 3;
In the example above, our console.log()
will only be called twice.
Avoid Circular References
In order for updates to flow, we must avoid any "circular references" or "cycles". This means that as an update is occurring, it must not reach the same node more than once.
For example, let's say we have two Computed properties, A and B. And let's say A references B, and B references A. So if A is updated, then we must update B. Similarly if B is updated, we must update A. In which case updating A, will update B, which will update A, which will update B, and so on.
So it is important that these situations be avoided.
Responding to updates
It is best to avoid writing to Observable properties inside of a Computed. In most cases, a second Computed which references the first will be enough.
Note: absolutely do not both read and write to an Observable inside of a Computed. This will cause a circular reference.
If you must write to an Observable or do other work, it is best to wrap it inside of a window.setTimeout()
, or use:
Cascade.wrapContext(callback: () => any, thisArg?: any): IObservable<any[]>;