<header> <h1 align="center"> <a href="https://github.com/guanghechen/sora/tree/@guanghechen/viewmodel@2.0.0/packages/viewmodel#readme">@guanghechen/viewmodel</a> </h1> <div align="center"> <a href="https://www.npmjs.com/package/@guanghechen/
npm install @guanghechen/viewmodel
@guanghechen/viewmodel
alt="Npm Version"
src="https://img.shields.io/npm/v/@guanghechen/viewmodel.svg"
/>
alt="Npm Download"
src="https://img.shields.io/npm/dm/@guanghechen/viewmodel.svg"
/>
alt="Npm License"
src="https://img.shields.io/npm/l/@guanghechen/viewmodel.svg"
/>
alt="Module Formats: cjs, esm"
src="https://img.shields.io/badge/module_formats-cjs%2C%20esm-green.svg"
/>
alt="Node.js Version"
src="https://img.shields.io/node/v/@guanghechen/viewmodel"
/>
alt="Tested with Jest"
src="https://img.shields.io/badge/tested_with-jest-9c465e.svg"
/>
alt="Code Style: prettier"
src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square"
/>
Reactive viewmodel implementation with observable state management and computed properties.
- npm
``bash`
npm install --save @guanghechen/viewmodel
- yarn
`bash`
yarn add @guanghechen/viewmodel
| 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 |
`typescript
import { ViewModel, State } from '@guanghechen/viewmodel'
import { Subscriber } from '@guanghechen/subscriber'
class CounterViewModel extends ViewModel {
public readonly count$ = new State
public readonly message$ = new State
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"
`
`typescript
import { ViewModel, State, Computed } from '@guanghechen/viewmodel'
class UserViewModel extends ViewModel {
public readonly firstName$ = new State
public readonly lastName$ = new State
public readonly fullName$: Computed
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"
`
`typescript
import { ViewModel, ObservableMap, ObservableSet } from '@guanghechen/viewmodel'
import { Map, Set } from 'immutable'
class TodoViewModel extends ViewModel {
public readonly todos$ = new ObservableMap
public readonly tags$ = new ObservableSet
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
}
`
`typescript
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 SSR
`
`typescript
import { ViewModel, State } from '@guanghechen/viewmodel'
import { Subscriber } from '@guanghechen/subscriber'
class DashboardViewModel extends ViewModel {
public readonly users$ = new State
public readonly orders$ = new State
public readonly revenue$ = new State
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)
}
}
``
- [homepage][homepage]
[homepage]:
https://github.com/guanghechen/sora/tree/@guanghechen/viewmodel@2.0.0/packages/viewmodel#readme