@guanghechen/viewmodel
v2.0.1
Published
<header> <h1 align="center"> <a href="https://github.com/guanghechen/sora/tree/@guanghechen/[email protected]/packages/viewmodel#readme">@guanghechen/viewmodel</a> </h1> <div align="center"> <a href="https://www.npmjs.com/package/@guanghechen/
Readme
Reactive viewmodel implementation with observable state management and computed properties.
Install
npm
npm install --save @guanghechen/viewmodelyarn
yarn add @guanghechen/viewmodel
Usage
| Name | Description |
| :-----------------: | :-------------------------------------------------------: |
| ViewModel | Abstract base class for reactive view models |
| State | Observable state with React-like setState API |
| Computed | Computed values derived from other observables |
| ObservableMap | Observable immutable map with change notifications |
| ObservableSet | Observable immutable set with change notifications |
Examples
Basic ViewModel
import { ViewModel, State } from '@guanghechen/viewmodel'
import { Subscriber } from '@guanghechen/subscriber'
class CounterViewModel extends ViewModel {
public readonly count$ = new State<number>(0)
public readonly message$ = new State<string>('Hello')
constructor() {
super()
this.registerDisposable(this.count$)
this.registerDisposable(this.message$)
}
public increment(): void {
this.count$.updateState(prev => prev + 1)
}
public setMessage(msg: string): void {
this.message$.updateState(msg)
}
}
const viewModel = new CounterViewModel()
// Subscribe to state changes
const subscriber = new Subscriber({
onNext: (count) => console.log('Count:', count)
})
viewModel.count$.subscribe(subscriber)
viewModel.increment() // Output: "Count: 1"
viewModel.increment() // Output: "Count: 2"Computed Properties
import { ViewModel, State, Computed } from '@guanghechen/viewmodel'
class UserViewModel extends ViewModel {
public readonly firstName$ = new State<string>('John')
public readonly lastName$ = new State<string>('Doe')
public readonly fullName$: Computed<string>
constructor() {
super()
this.registerDisposable(this.firstName$)
this.registerDisposable(this.lastName$)
// Create computed property
this.fullName$ = Computed.fromObservables(
[this.firstName$, this.lastName$],
([first, last]) => `${first} ${last}`
)
}
public setFirstName(name: string): void {
this.firstName$.updateState(name)
}
public setLastName(name: string): void {
this.lastName$.updateState(name)
}
}
const userVM = new UserViewModel()
console.log(userVM.fullName$.getSnapshot()) // "John Doe"
userVM.setFirstName('Jane')
console.log(userVM.fullName$.getSnapshot()) // "Jane Doe"Observable Collections
import { ViewModel, ObservableMap, ObservableSet } from '@guanghechen/viewmodel'
import { Map, Set } from 'immutable'
class TodoViewModel extends ViewModel {
public readonly todos$ = new ObservableMap<string, Todo>(Map())
public readonly tags$ = new ObservableSet<string>(Set())
constructor() {
super()
this.registerDisposable(this.todos$)
this.registerDisposable(this.tags$)
}
public addTodo(id: string, todo: Todo): void {
this.todos$.set(id, todo)
}
public removeTodo(id: string): void {
this.todos$.delete(id)
}
public addTag(tag: string): void {
this.tags$.add(tag)
}
public addMultipleTags(tags: string[]): void {
this.tags$.addAll(tags)
}
}
interface Todo {
title: string
completed: boolean
}State with React-like API
import { State } from '@guanghechen/viewmodel'
const userState = new State({ name: 'John', age: 30 })
// React-like setState with function
userState.setState(prev => ({ ...prev, age: prev.age + 1 }))
// Direct state update
userState.updateState({ name: 'Jane', age: 25 })
// Subscribe to changes
const unsubscribe = userState.subscribeStateChange(() => {
console.log('State changed:', userState.getSnapshot())
})
// React integration helpers
const snapshot = userState.getSnapshot() // For client-side
const serverSnapshot = userState.getServerSnapshot() // For SSRViewModel Ticker (Advanced)
import { ViewModel, State } from '@guanghechen/viewmodel'
import { Subscriber } from '@guanghechen/subscriber'
class DashboardViewModel extends ViewModel {
public readonly users$ = new State<number>(0)
public readonly orders$ = new State<number>(0)
public readonly revenue$ = new State<number>(0)
constructor() {
super()
this.registerDisposable(this.users$)
this.registerDisposable(this.orders$)
this.registerDisposable(this.revenue$)
}
public observeMetrics(): void {
// Create ticker that combines multiple observables
const metricsTicker = this.ticker(['users$', 'orders$', 'revenue$'])
const subscriber = new Subscriber({
onNext: () => {
const metrics = {
users: this.users$.getSnapshot(),
orders: this.orders$.getSnapshot(),
revenue: this.revenue$.getSnapshot()
}
console.log('Metrics updated:', metrics)
}
})
metricsTicker.ticker.subscribe(subscriber)
}
public updateMetrics(users: number, orders: number, revenue: number): void {
this.users$.updateState(users)
this.orders$.updateState(orders)
this.revenue$.updateState(revenue)
}
}