Skip to main content

Setup

Basic Setup

The new functional creation API lets you create and configure RxState in only one place.

Migration Guide

Read the following section for a migration guide explaining how to upgrade your codebase to the new API.

import { rxState } from '@rx-angular/state';
import { RxFor } from '@rx-angular/template/for';

@Component({
template: `<movie *rxFor="let movie of movies$" [movie]="movie" />`,
imports: [RxFor],
})
export class MovieListComponent {
private state = rxState<{ movies: Movie[] }>(({ set }) => {
// set initial state
set({ movies: [] });
});

// select a property for the template to consume as an observable
movies$ = this.state.select('movies');

// OR select a property for the template to consume as a signal
movies = this.state.signal('movies'); // Signal<Movie[]>
}

The functional approach will be the new default approach for newer versions.

Read the Migration Guide for a migration guide explaining how to upgrade your codebase to the new API.

Connect global state

Connect state slices from third-party services (e.g. NgRx Store)

Many people have problems combining observables with the component state in a clean way. Here is a use case where the @ngrx/store gets connected to the local state:

import { rxState } from '@rx-angular/state';

@Component({})
export class MovieListComponent {
private store = inject<Store<MovieState>>(Store);

private state = rxState<{ movies: Movie[] }>(({ set, connect }) => {
// set initial state
set({ movies: [] });

// connect global state to your local state
connect('movies', this.store.select('movies'));

// OR connect global state in form of a signal to your local state
connect('movies', this.store.selectSignal('movies'));
});
}

Store loading & error information

RxStates API makes it extremely easy to derive and store loading & error information when interacting with 3rd party data. Using one of the overloads of the connect method, we can fill our whole state object with only one connection.

import { Component, inject } from '@angular/core';
import { rxState } from '@rx-angular/state';
import { RxFor } from '@rx-angular/template/for';
import { RxIf } from '@rx-angular/template/if';
import { map, catchError, startWith, endWith } from 'rxjs';

@Component({
template: `
<loader *rxIf="loading$" />
<error *rxIf="error$" />
<movie *rxFor="let movie of movies$" [movie]="movie" />
`,
imports: [RxFor, RxIf],
})
export class MovieListComponent {
private movieResource = inject(MovieResource);

private state = rxState<{
movies: Movie[];
loading: boolean;
error: boolean;
}>(({ set, connect }) => {
// set initial state
set({ movies: [], loading: false, error: false });
// connect global state to your local state
connect(
this.movieResource.fetchMovies().pipe(
// map actual data
map((movies) => ({ movies })),
// in case of an error, store it
catchError(() => of({ error: true })),
// start with loading true
startWith({ loading: true }),
// when request completes, we can set loading to false
endWith({ loading: false })
)
);
});

// select a property for the template to consume
movies$ = this.state.select('movies');
loading$ = this.state.select('loading');
error$ = this.state.select('error');
}

Input Property Bindings

Combining Input bindings passing single values with RxState

info

As change detection is anyway executed when a new value arrives as input binding, you don't need to wrap that property with an async pipe in your template.

This approach is only suggested to use certain use cases.

  1. Your input property is mutated from withing your own component (see example below)
  2. You need that property in your state to compute other values
import { rxState } from '@rx-angular/state';
import { Subject } from 'rxjs';

@Component({
selector: 'app-count',
template: `
<div>{{ count$ | async }}</div>
<button (click)="increment.next()">increment</button>
<button (click)="decrement.next()">decrement</button>
`,
})
export class CounterComponent {
readonly count$ = this.state.select('count');

@Input() set count(count: number) {
this.state.set({ count });
}

increment = new Subject();
decrement = new Subject();

private state = rxState<{ count: number }>(({ set, connect }) => {
// set initial state
set({ count: 0 });
// increment
connect('count', this.increment, ({ count }) => count++);
// decrement
connect('count', this.decrement, ({ count }) => count--);
});
}

Combining Input bindings passing Observables

tip

You can save 1 change detection run per emission and improve performance of your application by providing Observables directly as Input. This way the ChangeDetection for the Input binding will only fire once for the first assignment.

You can use coerceObservable from @rx-angular/cdk/coercing to support static values as well as Observables with a single line of code.

import { rxState } from '@rx-angular/state';
import { Observable, Subject } from 'rxjs';
import { coerceObservable } from '@rx-angular/cdk/coercing';

@Component({
selector: 'app-count',
template: `
<div>{{ count$ | async }}</div>
<button (click)="increment.next()">increment</button>
<button (click)="decrement.next()">decrement</button>
`,
})
export class CounterComponent {
readonly count$ = this.state.select('count');

@Input() set count(count: Observable<number> | number) {
// You can use `coerceObservable` from `@rx-angular/cdk` to support static values as well as Observables with a single line of code.
this.state.connect('count', coerceObservable(count));
}

increment = new Subject();
decrement = new Subject();

private state = rxState<{ count: number }>(({ set, connect }) => {
// set initial state
set({ count: 0 });
// increment
connect('count', this.increment, ({ count }) => count++);
// decrement
connect('count', this.decrement, ({ count }) => count--);
});
}

Output Property Bindings

Combining Output bindings directly from RxState.

tip

For output bindings it is recommended to use the $ property. The $ property exposes the raw state without any selector benefits as memoization. This is important for output events, as events should not be stateful, e.g. repeat their latest value.

import { rxState } from '@rx-angular/state';
import { select } from '@rx-angular/state/selections';
import { Observable, Subject } from 'rxjs';
import { coerceObservable } from '@rx-angular/cdk/coercing';

@Component({
selector: 'app-count',
template: `
<div>{{ count$ | async }}</div>
<button (click)="increment.next()">increment</button>
<button (click)="decrement.next()">decrement</button>
`,
})
export class StatefulComponent {
readonly count$ = this.state.select('count');

@Input() set count(count: Observable<number> | number) {
// You can use `coerceObservable` from `@rx-angular/cdk` to support static values as well as Observables with a single line of code.
this.state.connect('count', coerceObservable(count));
}

@Output() countChange = this.state.$.pipe(select('count'));

increment = new Subject();
decrement = new Subject();

private state = rxState<{ count: number }>(({ set, connect }) => {
// set initial state
set({ count: 0 });
// increment
connect('count', this.increment, ({ count }) => count++);
// decrement
connect('count', this.decrement, ({ count }) => count--);
});
}

Updates based on previous state

Often it is needed calculate your new state based off some input and a previous state. The following example shows a local filtering algorithm implemented with RxState.

import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { rxState } from '@rx-angular/state';
import { RxFor } from '@rx-angular/template/for';

@Component({
template: `
<input placeholder="Search" [formControl]="search" />
<movie *rxFor="let movie of movies$" [movie]="movie" />
`,
imports: [RxFor, ReactiveFormsModule],
})
export class MovieListComponent {
private store = inject<Store<MovieState>>(Store);

search = new FormControl<string>();

private state = rxState<{ movies: Movie[] }>(({ set, connect }) => {
// set initial state
set({ movies: [] });
// connect global state to your local state
connect('movies', this.store.select('movies'));

// use the oldState and the searchInput to calculate the new state
connect('movies', this.search.valueChanges, (oldState, searchInput) => {
return oldState.movies.filter((movie) =>
movie.title.includes(searchInput)
);
});
});

// select a property for the template to consume
movies$ = this.state.select('movies');
}

Derive state using selections

tip

Instead of storing your derived state as properties in your state, use the selection APIs to derive them as new streams.

import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { rxState } from '@rx-angular/state';
import { RxFor } from '@rx-angular/template/for';

@Component({
template: `
<input placeholder="Search" [formControl]="search" />
<movie *rxFor="let movie of filteredMovies$" [movie]="movie" />
`,
imports: [RxFor, ReactiveFormsModule],
})
export class MovieListComponent {
private store = inject<Store<MovieState>>(Store);

search = new FormControl<string>();

private state = rxState<{ movies: Movie[]; searchValue: string }>(
({ set, connect }) => {
// set initial state
set({ movies: [], searchValue: string });
// connect global state to your local state
connect('movies', this.store.select('movies'));

// use the oldState and the searchInput to calculate the new state
connect('searchValue', this.search.valueChanges);
}
);

// derive filteredMovies$ from your stored state as an observable
filteredMovies$ = this.state.select(
['movies', 'searchValue'],
(movies, searchValue) => {
return movies.filter((movie) => movie.title.includes(searchValue));
}
); // Observable<Movie[]>

// derive filteredMovies from your stored state as a signal
filteredMovies = this.state.computed(({ movies, searchValue }) => {
return movies().filter((movie) => movie.title.includes(searchValue()));
}); // Signal<Movie[]>

// derive asynchronous filteredMovies from your stored state as a signal
filteredMovies = this.state.computedFrom(
select('searchValue'),
switchMap((searchValue) => this.movieResource.fetchMovies(searchValue)),
startWith([] as Movie[]) // needed as the initial value otherwise it will throw an error
); // Signal<Movie[]>
}

rxState in a Service

If you strive for a more sophisticated separation of concerns, you can simply use rxState as part of any @Injectable service.

import { inject, Injectable } from '@angular/core';
import { rxState } from '@rx-angular/state';

@Injectable({ providedIn: 'root' })
export class MovieService {
private resource = inject(MovieResource);

private state = rxState<{ movies: Movie[] }>(({ set, connect }) => {
// set initial state
set({ movies: [] });
// connect global state to your local state
connect('movies', this.resource.fetchMovies());
});

// select a property for the template to consume as an observable
movies$ = this.state.select('movies'); // Observable<Movie[]>

// select a property for the template to consume as a signal
movies = this.state.signal('movies'); // Signal<Movie[]>

// expose the select method
select: typeof this.state.select = this.state.select.bind(this.state);

// expose the signal method
signal: typeof this.state.signal = this.state.signal.bind(this.state);
}

rxState as DI Token

You can use the rxState function as a factory for an InjectionToken. This way you can still create DI State tokens.

Create a local Service by using rxState as factory function.

import { InjectionToken, inject } from '@angular/core';
import { rxState } from '@rx-angular/state';

export const MovieState = new InjectionToken({
factory: () => {
// inject dependencies here
const movieResource = inject(MovieResource);

return rxState<{ movies: Movie[] }>(({ set, connect }) => {
// set initial state
set({ movies: [] });
// connect global state to your local state
connect('movies', movieResource.fetchMovies());
});
},
});

Provide the Service inside the providers array when using a Component or Directive.

@Component({
template: ` <div>{{ viewState$ | async | json }}</div> `,
providers: [MovieState],
})
export class MovieListComponent {
private movieState = inject(MovieState);

viewState$ = this.movieState.select('movies');
}

Custom state accumulation (mutability)

The AccumulationFn is a function that runs on every state change and is responsible for building a new state from a given input. By default it merges together the state by spreading it - producing a new object on every change.

const defaultAccumulator: AccumulationFn = <T>(
state: T,
slice: Partial<T>
): T => {
return { ...state, ...slice };
};

Use setAccumulator to change that behavior. This way you could e.g. introduce immer as your accumulation to have full immutability.

import { produce } from 'immer';
import { rxState } from '@rx-angular/state';

const immerAccumulator = (state, slice) =>
produce(state, (draft) => {
Object.keys(slice).forEach((k) => {
draft[k] = slice[k];
});
});

state = rxState(({ setAccumulator }) => setAccumulator(immerAccumulator));

Or you can use any other custom deep-copying algorithm or simply go fully mutable.

const myAccumulator = (state: MyState, slice: Partial<MyState>) =>
deepCopy(state, slice);
state.setAccumulator(myAccumulator);

This can be done at runtime.

Migrate to new functional API

The new functional API provides a nicer developer experience and aligns with the new Angular APIs recently released. It will be the new default approach for using RxState and we want to emphasize everyone to use the new functional API. The following examples showcases the key differences and how to migrate from the class based approach to the functional one.

Providers

The beauty of the new functional approach is that it works without providers. This way, you simply use the new creation function rxState. Instead of importing RxState and putting it into the providers array, you now import rxState. The namespace still stays the same.

import { RxState } from '@rx-angular/state/state';
import { inject, Component } from '@angular/core';

@Component({
template: `<div>{{ state$ | async | json }}</div>`,
providers: [RxState],
})
export class MovieListComponent {
// expose state for the template
readonly viewState$ = this.state.select();

constructor(private state: RxState<{ movies: Movie[] }>) {
this.state.set({ movies: [] });
}
}

Manage side effects

info

The new rxState creation function drops the hold method and with it, it's capabilities of managing side effects. If you need to have such a feature, we encourage to use rxEffects.

import { RxState } from '@rx-angular/state';
import { Component } from '@angular/core';
import { Subject } from 'rxjs';

@Component({
template: `<movie
(click)="deleteClick$.next(item.id)"
*rxFor="let item of items$"
/>`,
providers: [RxState],
})
export class MovieListComponent {
readonly items$ = this.state.select('movies');

readonly deleteClick$ = new Subject();

constructor(
private state: RxState<{ movies: Movie[] }>,
private movieResource: MovieResource
) {
this.state.hold(
this.deleteClick$.pipe(concatMap((id) => this.movieResource.delete(id)))
);
}
}

Inheritance

info

The new rxState creation function does not return a class instance, thus no longer supporting inheritance.

If you wish, there is also the possibility of extending the RxState service. This can come in very handy for small components. Keep in mind you will expose the full RxState API to everyone having access to the component extending it.

import { RxState } from '@rx-angular/state';

@Directive({
selector: '[appStateful]',
})
export class StatefulComponent extends RxState<{ state: number }> {
readonly state$ = this.select();

constructor() {
super();
}
}

Disclaimer: this doc is work in progress. Not every use case has found its way into the docs. We encourage you to contribute 🙂.