withCngxAsyncState
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