Vex is a simple, lightweight, asynchronous state manager for JavaScript user interfaces.
npm install @dannymayer/vexVex is a simple, lightweight, asynchronous state manager for JavaScript user interfaces.
npm install @dannymayer/vex
> state$: Observable
An Observable of the manager's state. Will emit at least one value to any subscriber.
> dispatch(action: Action): void
Dispatches an Action.
> once(action: Action): Observable
Dispatches an Action and returns an Observable of the result.
> dispatches(actionType?: string): Observable
Returns an Observable that emits each time an action is dispatched, and before it
resolves. If an actionType is provided, filters the returned Observable to only emit
dispatches of that actionType.
> results(actionType?: string): Observable
Returns an Observable that emits each time an action is resolved. If an actionType is
provided, filters the returned Observable to only emit results of that actionType.
> parameter initialState: StateType
(required) Each manager must be initialized with an initialState.
> parameter options?: ManagerOptions (optional)
> allowConcurrency: boolean
(optional) Defaults to true; if set to false, an Action dispatched before
the previous Action has resolved will be queued and executed immediately when the
previous Action resolves (using RxJS's concatMap).
> type: string
(required) A string representing the category of the action.
> reduce(state: StateType): StateType
(required) The business logic associated with a synchronous Action. Analagous to a
reducer function in Redux, in that it returns the new state of the manager.
> resolve(state$: Observable
(required) The business logic associated with an asynchronous Action. Returns a
Promise or Observable of the new state of the manager.
> state: StateType
(required) A snapshot of the state at the time the ActionResult was created.
> actionType: string (required)
> error?: any (optional)
Vex integrates with Redux DevTools to allow you to visualize your app's state over time,
including the ability to time-travel through your app's history.
To configure DevTools, simply call setUpDevTools with an optional DevToolsOptions
as the only argument.
In Angular, setUpDevTools must be invoked inside of an NgZone#run callback, like so:
``ts
import { Component, NgZone } from '@angular/core'
import { setUpDevTools } from '@dannymayer/vex'
@Component(/ ... /)
export class AppComponent {
constructor(private _ngZone: NgZone) {
this._ngZone.run(() => setUpDevTools())
}
}
`
(all fields are optional)
> name: string
> maxAge: number
> latency: number
> actionsBlacklist: string[]
> actionsWhitelist: string[]
> shouldCatchErrors: boolean
> logTrace: boolean
> predicate: (state: any, action: any) => boolean
> shallow: boolean
Why Vex? The short answer: it's async by default!
The functional-reactive style enabled by RxJS has changed the way we approach asynchrony
in our code, and many state management frameworks have been built that use Observables to
model application state changing over time. Functional-reactive programming is also great
for doing asynchronous things like HTTP requests, but I haven't seen a state management
framework that embraces this at its core; support for "side-effects" always feels like an
add-on.
Well, I'm vexed.
I wanted my state manager to be simple and practical, and not too prescriptive; I want my
state management to feel like part of my app's architecture, rather than like something I
have to build my app around. I was frustrated with the amount of boilerplate in Flux-style
state management and with the fact that asynchrony felt like a second-class citizen. I
knew I wanted to keep the functional-reactive style of NgRx along with
the event-sourcing feel it inherits from Flux, and I
loved the ergonomic, low-boilerplate implementation that Akita
offers. Vex aims to check all of those boxes in one tiny, convenient interface.
app.model.ts
`ts
export interface AppState {
todos: string[]
}
export enum AppAction {
CREATE_TODO = 'CREATE_TODO'
DELETE_TODO = 'DELETE_TODO'
}
export const initialState: AppState = {
todos: []
}
`
app.service.ts
`ts
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { Manager } from '@dannymayer/vex'
import { Observable } from 'rxjs'
import { first, map, switchMap, withLatestFrom } from 'rxjs/operators'
import { AppAction, AppState } from './app.model'
@Injectable()
export class AppService {
constructor(
private _httpClient: HttpClient,
private _manager: Manager
) { }
// This method dispatches an asynchronous action.
// .dispatch(), which returns void, can be used in place of .once().
public createTodo(todo: string): Observable
return this._manager
.once({
type: AppAction.CREATE_TODO,
resolve: (state$) => this._httpClient.post('/api/todo', { todo }).pipe(
withLatestFrom(state$),
map(([state, response]) => ({
todos: [ ...state.todos, response ]
})),
),
})
.pipe(map(({ state }) => state))
}
// This method dispatches a synchronous action, and performs its asynchronous logic
// outside of the manager.
public deleteTodo(todoIndex: number): Observable
this._manager.dispatch({
type: AppAction.DELETE_TODO,
reduce: (state) => ({
...state,
todos: [
...state.todos.slice(0, todoIndex),
...state.todos.slice(todoIndex + 1),
],
}),
})
return this._httpClient.delete(/api/todo/${todoIndex}).pipe(
switchMap(() => this._manager.state$),
first(),
)
}
}
`
app.module.ts
`ts
import { HttpClientModule } from '@angular/common/http'
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { VexModule } from '@dannymayer/vex'
import { AppComponent } from './app.component'
import { initialState } from './app.model'
import { AppService } from './app.service'
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
VexModule.forRoot(initialState),
],
declarations: [ AppComponent ],
providers: [ AppService ],
bootstrap: [ AppComponent ],
})
export class AppModule { }
``