Store Status
Enable your store to track the loading, initialization and modification status. By utilizing the StoreStatusPlugin
, you can monitor whether your store is currently loading (running an effect) and if it has been initialized or modified.
Enabling Store Status
To activate the store status tracking feature in signalstory, include the plugin using the exposed useStoreStatus
factory method:
class StoreWithStatus extends Store<MyState> {
constructor() {
super({
initialState: { ... },
name: 'My Store with Status',
plugins: [
useStoreStatus()
],
});
}
}
Supported Effect Types
This plugin works only with effects objects. Generally, it works well with effects that return observables or promises which makes it particularly useful when dealing with asynchronous operations such as HTTP calls or other asynchronous tasks that modify or retrieve information from a store's state.
Effect returning Observables: The plugin can monitor the loading status and modification status of the store during the asynchronous execution of the observable. It works even if the effect's observable encounters errors, uses the filter operator, or operates as a hot observable (i.e., a subject).
const myObservableEffect = createEffect(
'My Observable Effect',
(store, params) => {
return inject(MyService)
.getData(params)
.pipe(
tap(data => {
store.set(data, 'Data Loaded');
})
);
}
);Effect returning Promises: the plugin fully supports effects that return promises, whether utilizing an async block or directly returning a promise.
// Example of using a promise in an effect
const myPromiseEffect = createEffect(
'My Promise Effect',
async (store, params) => {
const data = await inject(MyService).getData(params);
store.set(data, 'Data Loaded');
}
);
Considerations for Synchronous Effects
While the Store Status Plugin can technically work with synchronous effects, it's essential to know that change detection doesn't run during their execution. As a result, consumers won't be notified of status changes, making status tracking less beneficial for synchronous effects.
If you have a specific need to track status in synchronous effects, signalstory could potentially provide a solution by scheduling such effects using the macrotask queue. This approach introduces a slight delay in the execution of the effect, but it ensures the ability to notify consumers about status changes. Feel free to raise a request on GitHub for us to further explore and consider this implementation.
Limitations with Effects Using in-function-scope Subscribed Observables
If an effect contains an observable that is subscribed to within the effect function scope, the Store Status Plugin may not work as expected. This is because the plugin does not have information about the observable and cannot hook into its execution. In such cases, it's advisable to structure the effect to avoid subscribing to observables within the effect function.
const myNotSoObservableEffect = createEffect(
'My not so observable Effect',
(store, params) => {
inject(MyService)
.getData(params)
.subscribe(data => {
store.set(data, 'Data Loaded');
});
}
);
Tracking Running Effects
Check If Any Effect Is Running
The isAnyEffectRunning
function returns a Signal indicating whether any effect is currently running for any of the provided stores. If no stores are provided, it returns a signal indicating if any store has an effect running.
const anyEffectRunningSignal = isAnyEffectRunning(); // returns true if any store has any effect running
const singleEffectRunningSignal = isAnyEffectRunning(store); // returns true if the given store has any effect running
const multipleEffectRunningSignal = isAnyEffectRunning(store1, store2); // returns true if store1 or store2 has any effect running
Checking If a Specific Effect Is Running
The isEffectRunning
function returns a Signal indicating whether the specified effect is currently running for any of the provided stores. If no stores are provided, it returns a signal indicating if any store has the given effect running.
const anyEffectRunningSignal = isEffectRunning(effect); // returns true if any store has the specified effect running
const singleEffectRunningSignal = isEffectRunning(effect, store); // returns true if the given store has the specified effect running
const multipleEffectRunningSignal = isEffectRunning(effect, store1, store2); // returns true if store1 or store2 has the specified effect running
Manually Marking as Having No Running Effects
It should not happen but in rare situations where an effect is not automatically removed from the running state, you can manually mark a store as not having any running effects using the markAsHavingNoRunningEffects
function.
markAsHavingNoRunningEffects(store);
If you encounter such a situation, use this method as a temporary workaround and be sure to file an issue on GitHub for further investigation and resolution.
Tracking Loading Status
The isLoading
function provides a Signal indicating whether any of the specified stores is currently in a loading state. If no stores are provided, it returns a signal indicating if any store is in a loading state. This is a specialized feature of the prior section: Instead of tracking any running effect, it is scoped to marked effects.
const anyLoadingSignal = isLoading(); // returns true if any store is loading
const singelLoadingSignal = isLoading(store); // returns true if the given store is loading
const multipleLoadingSignal = isLoading(store1, store2); // returns true if store1 or store2 is loading
An effect created with setLoadingStatus
will mark the associated store as loading while the effect is running.
const effectThatLoadsBooks = createEffect(
'Load Books',
(store: BookStore): Observable<unknown> => {
return inject(BookService);
loadBooks().pipe(tap(books => store.setBookData(books.items)));
},
{
setLoadingStatus: true,
}
);
Tracking Initialization Status
The initialized
function returns a Signal indicating whether the specified store has been initialized by an initializing effect.
const initializedSignal = initialized(store);
A store is initially not initialized and any command (set
, update
, mutate
) applied to the store will not change this status.
Only an effect created with the setInitializedStatus
flag can set the store's initialization status to initialized.
const effectThatInitializesStore = createEffect(
'Load Books',
(store: BookStore): Observable<unknown> => {
return inject(BookService);
loadBooks().pipe(tap(books => store.setBookData(books.items)));
},
{
setInitializedStatus: true,
}
);
Tracking Modification Status
The modified
function returns a Signal indicating whether the specified store has been modified.
const modifiedSignal = modified(store);
A store is initially unmodified. Any command (set
, update
, mutate
) applied to the store will mark it as modified. Additionally, an effect created with the setInitializedStatus
flag can reset the store's modification status back to unmodified.
Reset store status
In some cases, it could be necessary to manually reset the store status to unmodified
and deinitialized
using the resetStoreStatus
function.
resetStoreStatus(store);