Core
Get method

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>

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