Usage
You can access the store by the store
factory and two direct methods: store.get()
and store.set()
.
Usually, all you need is a factory, which covers most of the cases. Direct access might be required for more advanced structures. For example, it is straightforward to create a paginated view with a list of data with the factory. Still, for infinite scroll behavior, you should display data from all of the pages, so you have to call store.get()
directly inside of the property getter.
Direct Methods
The store
factory uses direct methods internally. Because of that, it is important to understand how they work. The most important are the following three ground rules:
store.get()
always returns the current state of the model instance synchronouslystore.set()
always updates model instance asynchronously usingPromise
APIstore.get()
andstore.set()
always return an object (model instance, placeholder or promise instance)
Those unique principals unify access to async and sync sources. From the user perspective, it is irrelevant what kind of data source has the model. The store provides a placeholder type, which is returned if there is no previous value of the model instance (the model instance is not found, it is in pending state, or an error was returned). The placeholder protects access to its properties, so you won't use it by mistake (the guards help using the current state of the model instance properly).
store.get()
store.get()
arguments:
Model
- a model definitionid
- a string or an object representing identifier of the model instance
returns:
Model instance or model instance placeholder
The store.get
method always returns an object - model instance or a model placeholder. If the model source is synchronous (memory-based or external sync source, like localStorage
), the get method immediately returns an instance. Otherwise, depending on the cached value and validation, the placeholder might be returned instead. When the promise resolves, the next call to the store returns an instance. The cache mechanism takes care to notify the component that data has changed (if you need to use this method outside of the component definition, you can use store.pending()
guard to access the returned promise).
The above example uses a singleton memory-based model, so the data is available instantly. The count
property can be returned directly inside of the host property definition. Even the count
property of the host does not rely on other properties, the render
property will be notified when the current value of the GlobalState
changes (keep in mind that this approach creates a global state object, which is shared between all of the component instances).
store.set()
store.set()
The store.set()
method can create a new instance or update an existing model. According to the mode, the first argument should be a model definition or a model instance.
The set method always return a promise regardless of the type of data source. The model values are updated within the next microtask. However, the current state of the model instance will be updated instantly. After calling the set method the store.pending()
guard will return a truthy value, up to when the promise is resolved.
Create
arguments:
Model
- a model definitionvalues
- an object with partial values of the model instance
returns:
A promise, which resolves with the model instance
The singleton model has only one model instance, so it is irrelevant if you call store.set
method by the model definition, or the model instance - the effect will be the same. For example, in the above code snippet, Settings
can have a previous state, but setting new value by the model definition updates the already existing model instance.
Update
arguments:
modelInstance
- a model instancevalues
- an object with partial values of the model instance ornull
for deleting the model
returns:
A promise, which resolves to the model instance or placeholder (for model deletion)
The only valid argument for values besides an object instance is a null
pointer. It should be used to delete the model instance. However, as the last ground principle states, the store always returns an object. If the model instance does not exist, the placeholder is returned in the error state (with an error attached).
Partial Values
The store.set
supports partial values to update the model only with subset of values. If you use nested object structures, you can update them partially as well:
The above action will update only the myUser.address.street
value leaving the rest properties untouched (they will be copied from the last state of the model).
Factory
The factory defines a property descriptor connected to the store depending on the model definition configuration.
arguments:
Model
- a model definitionoptions
- an object with the following properties or the shorter syntax with the belowid
field valueid
- ahost
property name, or a function returning the identifier using thehost
draft
- a boolean switch for the draft mode, where the property returns a copy of the model instance for the form manipulation
returns:
a hybrid property descriptor, which resolves to a store model instance
Writable
If the model definition storage supports set action, the defined property will be writable using the store.set()
method internally. However, direct usage of the method is not required. Instead, use the assertion.
Singleton
If the model definition is a singleton, the id
field is not required, so you can define property without options.
Enumerable
For the enumerable model definition, the id
must be set (except the draft mode), either by the property name or a function.
Cache
The significant difference between using store.get()
method directly and the factory for enumerable models is a unique behavior implemented for returning the last resolved value when identifier has changed. The get method always returns the data according to the current state of the model. However, The factory caches the last value of the property, so when the id changes, the property still returns the previous state until the next instance is ready.
In above example when the page
changes, the userList
property still returns the last page with the pending state from the next instance. Because of that, you can avoid a situation when the user sees an empty screen with loading indicator - the old data are displayed until the new page is ready to be displayed. However, you have an option to hide data immediately - use store.pending()
guard for it.
Draft Mode
The draft mode provides a copy of the model instance or a new one with default values. The model definition used in draft mode is a memory-based version of the given model definition. The instance is deleted from the memory when component disconnects.
This mode can be especially useful when working with forms. If you want to use store to keep form values, which also supports validation, use draft mode. When all of the changes are finished, use the store.submit(draft)
method to create or update the primary model instance.
arguments:
Model
- an instance of the draft model definition
returns:
a promise resolving with the primary model instance
The store.submit()
method takes values from the draft and creates or updates primary model instance.
Combine store.value()
in the definition for validation, and the html.set(model, propertyPath)
helper from the template engine to update values without custom side effects (read more about the html.set
for the store in the Event Listeners
section of the template engine documentation).
Guards
The store provides three guard methods, which indicate the current state of the model instance. The returning value of those methods can be used for conditional rendering in the template. The pending
and error
also return additional information. The returning values are not exclusive, so there are situations when more than one of guard returns a truthy value.
Ready
arguments:
model
- a model instance
returns:
true
for a valid model instance,false
otherwise
The ready guard protects access to the models for async storage before they are fetched for the first time. You can also use it with sync storage, but if you are aware of the connection type, you can omit the guard.
The guard returns true
only for a valid model instance. If the model has changed, the previous state of the model is not valid anymore, so for that object, it will return false
.
When the model instance is going to be updated (by setting a new value, or by cache invalidation), the store returns the last valid state of the model until a new version is ready. In that situation store.ready()
still returns true
. It is up to you if you want to display a dirty state or not by combining ready and pending guards. It works the same if the update fails (then store.error()
will be truthy as well). In simple words, the store.ready()
always return true
if the model was resolved at least once.
Pending
arguments:
model
- a model instance
returns:
In pending state a promise instance resolving with the next model value,
false
otherwise
The pending guard returns a promise when a model instance is fetched from async storages, or when the instance is set (store.set()
method always use Promise API). If the model instance is returned (it is in a stable state), the guard returns false
.
Both pending and ready guards can be truthy if the already resolved model instance updates.
Error
arguments:
model
- a model instancepropertyName
- a property name of the failed validation defined withstore.value()
method
returns:
An error instance or whatever has been thrown or
false
. WhenpropertyName
is set, it returnserr.errors[propertyName]
orfalse
The error guard returns the value, which was thrown in the storage actions, usually an Error
instance.
Errors from Validation
The error guard can be used for access to the validation errors of the property defined with the store.value()
(look at the example in the Draft Mode section).
Last updated
Was this helpful?