Type-Safe Selectors

NGXS v18+ pushes toward stronger type safety for selectors. The key challenge is that state classes do not carry their model type as part of the selector contract — when you pass a state class to @Selector([MyState]) or createSelector([MyState], ...), TypeScript cannot verify the annotated parameter type against the actual state model.

This page shows how to move from less type-safe patterns to fully type-safe ones, and introduces the selector utilities designed for this purpose.

The Problem with State Classes as Selectors

When you write:

@Selector([ZooState])
static getAnimals(state: ZooStateModel) { ... }

The ZooStateModel annotation on state is not enforced by TypeScript — you could annotate it as any type and the compiler would not complain. The ZooState class does not carry model type information.

Using a StateToken

A StateToken ties the state name to its model type at the type level:

import { StateToken } from '@ngxs/store';

export const ZOO_STATE_TOKEN = new StateToken<ZooStateModel>('zoo');

Using the token as the name in @State and as the selector source makes the model type flow through automatically:

@State({ name: ZOO_STATE_TOKEN, defaults: { animals: [] } })
@Injectable()
export class ZooState {}

export class ZooSelectors {
  // TypeScript now enforces that `state` is ZooStateModel
  @Selector([ZOO_STATE_TOKEN])
  static getAnimals(state: ZooStateModel) {
    return state.animals;
  }
}

The token can also be passed directly to select() to select the full state model:

Wrapping an Existing State Class

If migrating to StateToken is not immediately possible, you can wrap an existing state class in a typed createSelector call:

This gives downstream selectors a properly-typed input without modifying the state class:

Slicing State with createPropertySelectors

Rather than writing a separate @Selector for every property of a model, createPropertySelectors generates a typed selector for each property automatically:

When passing a StateToken, no explicit type parameter is needed because the token already carries the model type. When passing a bare state class, you must supply the type manually:

The slices can be consumed directly in components:

Or composed into other selectors:

Selecting a Subset with createPickSelector

createPickSelector takes a typed selector and an array of keys, and returns a selector that emits only when one of those picked properties changes. This is useful when a component only cares about part of a larger model:

createPickSelector requires a strongly-typed selector or StateToken. Passing a bare state class loses type information for the picked properties.

Building View Models with createModelSelector

createModelSelector takes a map of selectors and returns a single selector whose output object has the same keys. This is the recommended way to assemble component view models from multiple states:

The output type is fully inferred from the selector map — no annotations required — provided each input selector is itself strongly typed (from a StateToken, createPropertySelectors, or an explicitly-typed @Selector).

Last updated