Model get
method
The method is one of only two methods built-in the Model class. Naturally, it is used to access state as well as to observe it.
Overloads
-
Get values from state
get(): State<this>
-
Create a subscriber
get(effect: Effect<this>): () => void
-
Get value from state
get<T>(key: T, required?: boolean): Value<this, T>
-
Call a function when update occurs
get<T>(key: T, callback: OnUpdate<this, T>): () => void
-
Check if model is expired
get(status: null): boolean
-
Callback on destroyed
get(status: null, callback: () => void): () => void
Get values from state
get(): State<this>
Retrieves the full state from controller. This is also recursive - if the model has nested models, their state will be included as well.
class Nested extends Model {
foo = 1;
bar = 2;
}
class Control extends Model {
nested = new Nested();
foo = 'foo';
bar = 'bar';
baz = 'baz';
}
const control = new Control();
const state = control.get();
{
"nested": {
"foo": 1,
"bar": 2
},
"foo": "foo",
"bar": "bar",
"baz": "baz"
}
Create a subscriber
get(effect: Effect<this>): () => void
Create a subscriber, from an "effect" function, called any time state changes. Subscribers are intelligent, and only respond to changes for the properties they access.
They do this by wrapping the source controller in a proxy and passing it to the effect. When a property is accessed, the value is returned and added to a dependency list. When an update overlaps, the subscriber is called again, and the process repeats.
class Control extends Model {
foo = 'foo';
bar = 'bar';
baz = 'baz';
}
const control = new Control();
control.get(current => {
console.log('foo: ', current.foo);
});
This example will log foo: foo
immediately, and now also when foo
changes.
It will do this until the subscriber or controller itself is destroyed.
// foo: foo (immediately)
control.foo = 'bar';
// foo: bar (async)
control.bar = 'baz';
// Will not be called because this subscriber doesn't access bar.
Given this behavior, it is recommended you always destructure the current state in your effect, to ensure your code is clear and bug-free. In situations where you use a value conditionally, you still want to update your effect to up to date.
control.get(state => {
const { foo, bar, baz } = state;
if(foo)
console.log('bar:', bar);
console.log('baz:', baz);
});
Silent access
Sometimes, you might want to access properties without making them a dependency.
For this, you can destructure is
property. It is a reference
to the real controller, without proxy getters to detect access.
control.get(state => {
const { is, foo } = state;
console.log('foo:', foo);
console.log('bar:', is.bar);
});
Here, this effect will only refresh when foo
changes, but can still log bar
when it does.
Stop subscription
A subscriber is automatically canceled when parent controller is destroyed, however you might need to cancel it manually.
For this, a function is returned, which can be called to halt updates.
const cancel = control.get(current => {
console.log(`Foo is currently ${current.foo}`);
});
// Foo is currently foo (immediately)
control.foo = 'bar';
// Foo is currently bar (async)
cancel();
control.foo = 'bar';
// Will not be called
A subscriber may be canceled by returning null
from the effect function itself.
control.get(current => {
console.log(`Foo is currently ${current.foo}`);
if(current.foo === 'bar'){
console.log('Canceling subscription');
return null;
}
});
// Foo is currently foo (immediately)
control.foo = 'bar';
// Foo is currently bar (async)
// Canceling subscription
control.foo = 'baz';
// Will not be called
Effect Callback
An effect function may return a callback, which is called with one argument update
when effect becomes stale.
It will receive one of three values, depending on the reason for callback:
true
: A dependency updated; effect will be called again.false
: The subscriber was destroyed, and will not be called again.null
: The controller was destroyed; all listeners are cancelled.
const done = control.get(current => {
console.log('foo:', current.foo);
return (update) => {
if(update === null)
console.log('effect destroyed');
};
});
This happens when a value which was accessed, has changed again. This happens synchronously however, before the effect is called again (if necessary).
Lifecycle mode
Subscribers which do not access any values can be considered in "lifecycle mode". They will be called immediately, and may return a function to be called when the subscriber (or the controller itself) is destroyed.
These are especially useful when called from constructor
, as controller will
not be observable before the initial event.
class Control extends Model {
constructor(){
super();
this.get(() => {
console.log('Model is ready');
return () => {
console.log('Model is destroyed');
}
});
}
}
Get value from state
get<T>(key: T, required?: boolean): Value<this, T>
Retrieves a value from state.
By default, if a value does not need it will return undefined
,
however will throw if required
is true.
class Control extends Model {
foo = 'foo';
}
const control = new Control();
control.get('foo'); // foo
control.get('bar'); // undefined
control.get('baz', true); // Throws
Callback on event
get<T>(key: T, callback: OnUpdate<this, T>): () => void
Calls a function when a value is updated. This update is synchronous, and will be called before any subscribers are notified.
class Control extends Model {
foo = 'foo';
}
const control = new Control();
control.get('foo', (key, value, source) => {
console.log(`Updated ${source}.${key}: "${value}"`);
});
control.foo = 'bar';
// Updated Control.foo: "bar"
Check if expired
get(status: null): boolean
Returns true
if the model is destroyed, otherwise false
.
const control = Model.new();
control.get(null);
// false
control.set(null);
// destroys controller
control.get(null);
// true
Callback on destroy
get(status: null, callback: () => void): () => void
Calls a function when the model is destroyed. This is called synchronously on null
event.
Returned a function will remove the event listener.
const control = Model.new();
control.get(null, () => {
console.log('Model is destroyed');
});
control.set(null);
// Model is destroyed