createEeffct
The createEffect
helper allows you to react to state changes in your store(s) without manually managing subscriptions.
It is similar in spirit to NgRx Effects, but simpler and store-centric rather than action-centric.
Unlike NgRx, where effects listen to actions, here effects listen directly to state changes. This makes them especially useful for synchronizing data, triggering side-effects, or reacting to multiple store paths at once.
How it works
You pass one or more paths (or observables) to watch.
createEffect
subscribes internally to those changes.On every change, it calls your callback with the old value, new value, and a hasChanges flag.
Subscriptions are automatically cleaned up when the Angular
DestroyRef
triggers (e.g. when the host component/service is destroyed).
Overload signatures
// 1. Watch multiple observables (from different stores, or custom streams)
export function createEffect<T>(
sources: Observable<T>[],
callback: (changes: { [key: string]: WatchResult<T> }) => void
): void;
// 2. Watch multiple paths in a single store
export function createEffect<T>(
store: Store<T>,
paths: (string | string[])[],
callback: (changes: { [key: string]: WatchResult<T> }) => void
): void;
WatchResult<T>
WatchResult<T>
Each entry in the callback contains:
type WatchResult<T> = {
oldValue: T | undefined;
newValue: T | undefined;
hasChanges: boolean;
};
Example: Single path
createEffect(store, ['layout/test'], (result) => {
const layout = result['layout/test'];
if (layout.oldValue === undefined) {
console.log('Initial value:', layout.newValue);
} else if (layout.hasChanges) {
console.log('Changed from', layout.oldValue, 'to', layout.newValue);
}
});
store.update(state => state.layout.test = 'updated value');
Output:
Initial value: test2
Changed from test2 to updated value
Example: Multiple paths
createEffect(store, ['layout/test', 'user/name'], (result) => {
if (result['layout/test'].hasChanges) {
console.log('Layout changed:', result['layout/test']);
}
if (result['user/name'].hasChanges) {
console.log('User name changed:', result['user/name']);
}
});
store.update(state => state.layout.test = 'another');
store.update(state => state.user.name = 'Charlie');
Output:
Layout changed: { oldValue: 'test2', newValue: 'another', hasChanges: true }
User name changed: { oldValue: 'Bob', newValue: 'Charlie', hasChanges: true }
Example: Multiple stores
createEffect(
[userStore.select('name'), settingsStore.select('theme')],
(result) => {
if (result['observable0'].hasChanges) {
console.log('User name changed:', result['observable0']);
}
if (result['observable1'].hasChanges) {
console.log('Theme changed:', result['observable1']);
}
}
);
userStore.update(state => state.name = 'Bob');
settingsStore.update(state => state.theme = 'dark');
Comparison with NgRx createEffect
createEffect
Feature
@ng-state/store
createEffect
NgRx createEffect
Trigger
State path(s) or observables
Actions
Input
Store values
Action stream
Output
Side-effects (user-defined callback)
New actions (dispatched automatically)
Boilerplate
Minimal
Requires actions, effect classes
Typing
Based on store state shape
Based on action types
DevTools traceability
Traced via state mutations
Traced via action log
Use case
React to store value changes (UI-driven logic)
React to domain events (action-driven)
Last updated
Was this helpful?