-
Notifications
You must be signed in to change notification settings - Fork 5
Observers
Everything in Statesman.js is observable, meaning you can attach callbacks to the model that fire when the part we're interested in changes.
Let's create an empty model:
var state = new Statesman();
You can observe foo
with
state.observe( 'foo', function ( newValue, oldValue ) {
alert( 'foo changed from ' + oldValue + ' to ' + newValue );
});
state.set( 'foo', 'bar' ); // alerts 'foo changed from undefined to bar'
state.set( 'foo', 'baz' ); // alerts 'foo changed from bar to baz'
state.observe
returns an array of observers, which can be passed to state.unobserve
to cancel themselves:
var observers = state.observe( 'foo', function ( newValue, oldValue ) {
alert( newValue );
});
state.unobserve( observers );
state.set( 'foo', 'bar' ); // does nothing - the observer is not notified
(Boring technical detail: it returns an array, because under the hood state.observe
sets up observers for the specified keypath, and each of the upstream keypaths. Hence state.observe( 'foo.bar.baz[0]', callback )
will return an array of four observers - one for 'foo.bar.baz[0]', one for 'foo.bar.baz', one for 'foo.bar', and one for 'foo'. Don't worry though - the specified callback
is shared by all the observers, it doesn't create anonymous wrapper functions or do anything else that wastes memory.)
Ordinarily, you only want to be notified when something has changed, and so when state.set
is called, observers are notified on that basis. However you can force an observer to be notified like so:
state = new Statesman({ foo: bar });
state.observe( 'foo', alert );
state.set( 'foo', 'bar' ); // does nothing
state.set( 'foo', 'bar', { force: true }); // alerts 'bar' even though it hasn't changed
Conversely, there may be occasions when you want to update the model without notifying observers:
state.set( 'foo', 'baz', { silent: true }); // does nothing, even though it HAS changed
You can't have both silent
and force
set to true
- if you do, force
will be ignored.
A word of caution: the equality check only applies to primitives (strings, numbers, booleans etc) - not to objects and arrays, which will always trigger notifications:
state = new Statesman({ foo: myArray });
state.observe( 'foo', alert );
state.set( 'foo', myArray ); // alerts '[object Array]' even though it's the same array
This is because downstream keypaths of those objects and arrays may have changed even though it's the same 'chunk of memory' in the system, and deep equality checks are expensive.
Often, you may want your observers to trigger immediately, rather than waiting for the first time state.set
gets called. You can make that happen by passing in true
as the third argument to state.observe
:
state.set( 'foo', 'bar' );
state.observe( 'foo', function ( newValue, oldValue ) {
alert( newValue ); // alerts 'bar' immediately, because the third argument is true
}, true );
state.set( 'foo', 'baz' ); // alerts 'baz' as you'd expect
(Question: should this be the default behaviour? Answers on a postcard to @rich_harris please.)
If you know that a value will only be set once (for example, after some data has been loaded via AJAX or whatever), it's good hygiene to remove your observers after it has happened. state.observeOnce
gives us a convenient way to do so:
state.observeOnce( 'foo', function ( newValue, oldValue ) {
alert( newValue );
});
state.set( 'foo', 'bar' ); // alerts 'bar'
state.set( 'foo', 'baz' ); // does nothing - the observer has already been removed
Next: computed values