createViewModel
Overview
Section titled “Overview”createViewModel combines multiple Observable sources into a single projected view model using combineLatest, map, and distinctUntilChanged. It is the recommended way to compose data from several stores or streams into a UI-ready shape that only emits when the projected output actually changes.
Import
Section titled “Import”import { createViewModel } from "rxjs-ai";Basic usage
Section titled “Basic usage”import { createStore, createViewModel } from "rxjs-ai";
// Two independent storesconst userStore = createStore({ name: "Alice", role: "admin" });const uiStore = createStore({ theme: "dark", sidebarOpen: true });
// Combine selectors into a single view modelconst vm$ = createViewModel( { userName: userStore.select((s) => s.name), role: userStore.select((s) => s.role), theme: uiStore.select((s) => s.theme), sidebarOpen: uiStore.select((s) => s.sidebarOpen), }, (sources) => ({ greeting: `Hello, ${sources.userName}`, isAdmin: sources.role === "admin", theme: sources.theme, sidebarOpen: sources.sidebarOpen, }),);
vm$.subscribe((vm) => { console.log(vm.greeting); // "Hello, Alice" console.log(vm.isAdmin); // true console.log(vm.theme); // "dark"});The returned Observable only emits when the projected output changes, preventing unnecessary side-effects downstream.
Custom comparator
Section titled “Custom comparator”By default, createViewModel uses Object.is on the projected result to determine whether a new emission should be sent to subscribers. For projected objects (which create a new reference on every emission), you should provide a custom comparator:
import { isEqual } from "lodash-es";
const vm$ = createViewModel( { items: store.select((s) => s.items), filter: store.select((s) => s.filter), }, ({ items, filter }) => ({ visibleItems: items.filter((item) => item.category === filter), totalCount: items.length, }), isEqual, // deep equality prevents duplicate emissions);You can also use a field-level comparator for more granular control:
const vm$ = createViewModel( { items: store.select((s) => s.items), query: store.select((s) => s.query), }, ({ items, query }) => ({ results: items.filter((i) => i.name.includes(query)), query, }), (prev, next) => prev.query === next.query && prev.results.length === next.results.length,);function createViewModel< Sources extends ObservableMap, ViewModel,>( sources: Sources, projector: (values: ObservableValues<Sources>) => ViewModel, compare: (previous: ViewModel, current: ViewModel) => boolean = Object.is,): Observable<ViewModel>;
// wheretype ObservableMap = Record<string, Observable<unknown>>;| Parameter | Type | Description |
|---|---|---|
sources | ObservableMap (Record<string, Observable<unknown>>) | A named map of Observable sources. Each key becomes a property on the object passed to the projector. |
projector | (values: ObservableValues<Sources>) => ViewModel | A pure function that receives the latest values from all sources and returns the projected view model shape. |
compare | (previous: ViewModel, current: ViewModel) => boolean | Equality function used by distinctUntilChanged. Defaults to Object.is. Return true when the two values should be considered equal (suppressing the emission). |
Returns: Observable<ViewModel> — an Observable that emits the projected view model whenever any source emits and the projected output differs from the previous emission.