Skip to main content

Setup

Basic Setup

Compose

The default way of using the RxState service is by providing a local instance bound to the component's lifecycle. This way, you have complete control over the API and what you want to expose.

@Component({
selector: 'app-stateful',
template: `<div>{{ state$ | async | json }}</div>`,
providers: [RxState],
})
export class StatefulComponent {
readonly state$ = this.state.select();

constructor(private state: RxState<{ foo: string }>) {}
}

Inherit

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.

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

constructor() {
super();
}
}

Connect global state

Connect state slices from third-party services (e.g. NgRx Store) or trigger them from side-effects

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:

@Component({
selector: 'app-stateful',
template: ` <div>{{ (state$ | async).count }}</div> `,
providers: [RxState],
})
export class StatefulComponent {
readonly state$ = this.state.select();

constructor(
private state: RxState<{ count: number }>,
private store: Store<AppState>
) {
state.connect('count', store.select('count'));
}
}

Input Property Bindings

Combining Input bindings passing single values with RxState

@Component({
selector: 'app-stateful',
template: ` <div>{{ title$ | async }}</div> `,
providers: [RxState],
})
export class StatefulComponent {
readonly title$ = this.state.select('title');

@Input() set title(title: string) {
this.state.set({ title });
}

constructor(private state: RxState<{ title: string }>) {}
}

Combining Input bindings passing Observables with RxState

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


const initialState: ComponentState = {
title: 'MyComponent',
showButton: false,
count: 0,
};

@Component({
selector: 'app-stateful',
template: ` <div>{{ (state$ | async).count }}</div> `,
providers: [RxState],
})
export class StatefulComponent {
@Input() set config(count$: Observable<ComponentStateInput>) {
this.state.connect('count', count$);
}
constructor(private state: RxState<{ count: number }>) {}
}

Output Property Bindings

Combining Output bindings directly from RxState

@Component({
selector: 'app-stateful',
template: ` <div (click)="onClick($event)">Increment</div> `,
providers: [RxState],
})
export class StatefulComponent {
@Output() countChange = this.state.$.pipe(select('count'));

constructor(private state: RxState<{ count: number }>) {}

onClick() {
this.state.set(({ count }) => {
count: count++;
});
}
}

Updates based on previous state

Often it is needed to get the previous state to calculate the new one.

@Component({
selector: 'app-stateful',
template: `
<ul>
<li *ngFor="let item of items$ | async">
{{ item }}
<button (click)="btnClick$.next(item.id)">remove</button>
</li>
</ul>
`,
providers: [RxState],
})
export class StatefulComponent {
readonly items$ = this.state.select('list');
readonly btnClick$ = new Subject();

constructor(private state: RxState<{ list: { id: number }[] }>) {
this.state.connect(this.btnClick$, (state, id) => ({
...state,
list: state.list.filter((i) => i.id !== id),
}));
}
}

Usage with services

If you strive for a more sophisticated separation of concerns, you can extend the RxState in a locally provided Service.

Create a local Service by extending the RxState

interface StatefulComponentState {
foo: number;
}
@Injectable()
export class StatefulComponentService extends RxState<StatefulComponentState> {
readonly state$ = this.select();

constructor() {
super();
}
}

Provide the Service inside the using Component or Directive

@Component({
selector: 'app-stateful',
template: ` <div>{{ viewState$ | async | json }}</div> `,
providers: [StatefulComponentService],
})
export class StatefulComponent {
readonly viewState$ = this.state.state$;

constructor(private state: StatefulComponentService) {}
}

Manage side effects

@Component({
selector: 'app-stateful',
template: `<ul>
<li *ngFor="let item of items$ | async">
{{ item }}
<button (click)="deleteClick$.next(item.id)">remove</button>
</li>
</ul> `,
providers: [RxState],
})
export class StatefulComponent {
readonly items$ = this.state.select('list');
readonly deleteClick$ = new Subject<number>();

constructor(
private state: RxState<{ list: { id: number }[] }>,
private apiService: ApiService
) {
this.state.hold(
this.deleteClick$.pipe(concatMap((id) => this.apiService.delete(id)))
);
}
}

setAccumulator and deep-copying state

Use setAccumulator to update state via deep-copies.

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

This can be done at runtime.

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