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>

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

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?