Skip to main content

Selecting the ViewModel

Here are some useful strategies to properly handle ViewModels with @rx-angular/state. In this examples we will use standalone selectSlice operator.

Imagine the following setup:

interface Item {
id: string;
name: string;
}

interface ComponentState {
title: string;
created: string;
list: Item[];
visibleItemIds: string[];
}

interface ComponentViewModel {
title: string;
created: string;
visibleItems: Item[];
total: number;
}

You want to render the following template.

<ng-container *ngIf="viewModel$ | async as vm">
<h1>
{{vm.title}}
<small>{{vm.created | date}} -<b>total: {{vm.total}}</b></small>
</h1>
<ul>
<li *ngFor="let item of vm.visibleItems">{{ item.name }}</li>
</ul>
</ng-container>

As your view requires additional and/or derived information from your component state, we need to transform the ComponentState state into another shape (ComponentViewModel) Changes of your viewModel$ ultimately result in component renderings, so we have to make sure that it's emissions are distinct.

Using selectSlice:

For this scenario we created the selectSlice operator. It returns an Observable that emits a distinct subset of the received object.

Utilizing it inside of the RxState#select method enables you to pluck a distinct ViewModel directly out of your state.

@Component()
export class ViewModelComponent extends RxState<ComponentState> {
readonly viewModel$: Observable<ComponentViewModel> = this.select(
selectSlice(['title', 'list', 'created', 'visibleItemIds']),
map(({ title, list, created, visibleItemIds }) => ({
title,
created,
total: list.length,
visibleItems: list.filter((item) =>
visibleItemIds.some((itemId) => itemId === item.id)
),
}))
);
constructor() {
super();
}
}

Multiple Observables and selectSlice:

There are situations where you want to divide your ViewModel into different parts.

Imagine the following view: This way you may achieve more control over what to render when, e.g. lazy rendering.

<ng-container *ngIf="viewModel.main$ | async as vm">
<h1>
{{vm.title}}
<small> {{vm.created | date}} </small>
</h1>
</ng-container>
<ng-container *ngIf="viewModel.list$ | async as vm">
<div><b>total: {{vm.total}}</b></div>
<ul>
<li *ngFor="let item of vm.visibleItems">{{ item.name }}</li>
</ul>
</ng-container>
interface ComponentViewModel {
main$: Observable<{ title: string; created: Date }>;
list$: Observable<{ total: number; visibleItems: Item[] }>;
}

@Component()
export class ViewModelComponent extends RxState<ComponentState> {
readonly viewModel: ComponentViewModel = {
main$: this.state.select(selectSlice(['title', 'created'])),
list$: this.state.select(
selectSlice(['list', 'visibleItemIds']),
map(({ list, visibleItemIds }) => ({
total: list.length,
visibleItems: list.filter((item) =>
visibleItemIds.some((itemId) => itemId === item.id)
),
}))
),
};
constructor() {
super();
}
}