Skip to main content
cngx-src documentation

withCngxAsyncState

Functioninterop/signals

projects/interop/signals/with-cngx-async-state.ts

Description#

@ngrx/signals store feature that grants a store a CngxAsyncState-shaped slice with zero hand-written status flags.

The element type is supplied up front and the key is inferred from the argument, so the two contributed members carry the literal key: withCngxAsyncState<User[]>()('users') adds usersState (the read-only CngxAsyncState<User[]> view) and usersSink (the writable ManualAsyncState<User[]>). Drive the status with the existing tapAsyncState operator - no new state machine, no manual isLoading boolean. Both members are the same underlying createManualState instance, exposed under a read type and a write type.

The <T>() / (key) split is deliberate: TypeScript cannot infer the literal key while T is given explicitly in one call, so a single-call form collapses the key to a string index signature. Currying keeps the key literal - and therefore the store members concretely named.

Key collisions are not guarded by cngx: the feature contributes ${key}State / ${key}Sink via withProps and relies on NgRx SignalStore's own dev-mode duplicate-prop warnings to surface a clash. Pick keys that do not shadow existing state or props on the store.

export const UsersStore = signalStore(
  withCngxAsyncState<User[]>()('users'),
  withMethods((store) => {
    const http = inject(HttpClient);
    return {
      load: () =>
        http.get<User[]>('/api/users').pipe(
          tapAsyncState(store.usersSink),
          takeUntilDestroyed(),
        ).subscribe({ error: () => {} }), // sink owns the failure; keep the global handler quiet
    };
  }),
);
// store.usersState.status(), store.usersState.data() - derived, no flags