Derived State
It is common to need to cache the result of expensive work that does not represent true alterations in application state. Derived state exists to store this kind of data in memory and keep it up to date when the underlying observable state changes.
DeriveDefinition
Derived state has all of the same issues with storage and retrieval that normal state does. Similar
to KeyDefinition
, derived state depends on DeriveDefinition
s to define magic string keys to
store and retrieve data from a cache. Unlike normal state, derived state is always stored in memory.
It still takes a StateDefinition
, but this is used only to define a namespace for the derived
state, the storage location is ignored. This can lead to collisions if you use the same key for two
different derived state definitions in the same namespace.
Derive definitions can be created in two ways:
new DeriveDefinition(STATE_DEFINITION, "uniqueKey", _DeriveOptions_);
// or
const keyDefinition: KeyDefinition<T>;
DeriveDefinition.from(keyDefinition, _DeriveOptions_);
The first allows building from basic building blocks, the second recognizes that derived state is
often built from existing state and allows you to create a definition from an existing
KeyDefinition
. The resulting DeriveDefinition
will have the same state namespace, key, and
TFrom
type as the KeyDefinition
it was built from.
Type Parameters
DeriveDefinition
s have three type parameters:
TFrom
: The type of the state that the derived state is built from.TTo
: The type of the derived state.TDeps
: defines the dependencies required to derive the state. This is further discussed in Derive Definition Options.
DeriveDefinitionOptions
The DeriveDefinition
section specifies a third parameter as
_DeriveOptions_
, which is used to fully specify the way to transform TFrom
to TTo
.
deserializer
- For the same reasons as Key Definition Options,DeriveDefinition
s require have adeserializer
function that is used to convert the stored data back into theTTo
type.derive
- A function that takes the current state and returns the derived state. This function takes two parameters:from
- The latest value of the parent state.deps
- dependencies used to instantiate the derived state. These are provided when theDerivedState
class is instantiated. This object should contain all of the application runtime dependencies for transform the from parent state to the derived state.
cleanupDelayMs
(optional) - Takes the number of milliseconds to wait before cleaning up the state after the last subscriber unsubscribes. Defaults to 1000ms. If you have a particularly expensive operation, such as decryption of a vault, it may be worth increasing this value to avoid unnecessary recomputation.
Specifying dependencies required for your derive
function is done through the type parameters on
DerivedState
.
new DerivedState<TFrom, TTo, { example: Dependency }>();
would require a deps
object with an example
property of type Dependency
to be passed to any
DerivedState
configured to use the DerivedDefinition
.
Both derive
and deserializer
functions should take null inputs into consideration. Both parent
state and stored data for deserialization can be null
or undefined
.
DerivedStateProvider
The DerivedState<TFrom, TTo, TDeps>
class has a purpose-built provider which instantiates the
correct DerivedState
implementation for a given application context. These derived states are
cached within a context, so that multiple instances of the same derived state will share the same
underlying cache, based on the DeriveDefinition
used to create them.
Instantiating a DerivedState
instance requires an observable parent state, the derive definition,
and an object containing the dependencies defined in the DeriveDefinition
type parameters.
interface DerivedStateProvider {
get: <TFrom, TTo, TDeps extends DerivedStateDependencies>(
parentState$: Observable<TFrom>,
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
dependencies: TDeps,
) => DerivedState<TTo>;
}
Any observable can be used as the parent state. If you need to perform some kind of work on data
stored to disk prior to sending to your derive
functions, that is supported.
DerivedState
DerivedState
is intended to be built with a provider rather than directly instantiated. The
interface consists of two items:
interface DerivedState<T> {
state$: Observable<T>;
forceValue(value: T): Promise<T>;
}
state$
- An observable that emits the current value of the derived state and emits new values whenever the parent state changes.forceValue
- A function that takes a value and immediately setsstate$
to that value. This is useful for clearing derived state from memory without impacting the parent state, such as during logout.
forceValue
forces state$
once. It does not prevent the derived state from being recomputed
when the parent state changes.