@lacolaco/reactive-store
v5.0.0
Published
A minimal implementation of state management with RxJS
Downloads
39
Readme
@lacolaco/reactive-store
Very simple store implementation for state management with RxJS.
https://yarn.pm/@lacolaco/reactive-store
Install
$ yarn add rxjs @lacolaco/reactive-store
Concept
- RxJS: Use the ecosystem
- TypeScript: Type-safe state management
- Simple: Easy to understand what the library does and doesn't
How to Use
Create a store: new Store({ initialValue })
import { Store } from '@lacolaco/reactive-store';
export interface CounterState {
count: number;
}
export const initialValue: CounterState = {
count: 0,
};
export const counterStore = new Store<CounterState>({ initialValue });
Use the store
Get the current value: .value: T
export const counterStore = new Store<CounterState>({ initialValue: 1 });
console.log(counterStore.value); // => 1
Observe value changes: .valueChanges: Observable<T>
.valueChange
returns a raw observable of the store.
export const counterStore = new Store<CounterState>({ initialValue: 1 });
counterStore.valueChanges.subscribe(value => {
console.log(value); // => 1
});
// You can use `pipe` and operators of RxJS.
const doubled$: Observable<number> = counterStore.valueChanges.pipe(map(value => value * 2));
Update the store: .update((value: T) => T): void
update
takes a function which takes the current value and returns a new value.
export const counterStore = new Store<CounterState>({ initialValue: 1 });
counterStore.update(value => value + 1);
console.log(counterStore.value); // => 2
Observe scoped value: .select((value: T) => U): Observable<U>
select
method is for mapping and memoize the scoped value.
This is using internally it uses RxJS's map
and distinctUntilChanged
operators.
export const counterStore = new Store<CounterState>({
initialValue: { count: 1 },
});
counterStore.valueChanges.subscribe(value => {
console.log(value); // => { count: 1 }
});
const selected$: Observable<number> = counterStore.select(value => value.count);
selected$.subscribe(value => {
console.log(value); // => 1
});
Listen update events: .storeUpdateChanges: Observable<StoreUpdateChange<T>>
A store dispatchs a change event every time updating the store. This is for debugging or integrating with other tools.
const counterStore = new Store<CounterState>({
initialValue,
});
counterStore.storeUpdateChanges.subscribe(change => {
console.log(`Previous Value`, change.previousValue);
console.log(`Current Value`, change.currentValue);
console.log(`Label`, change.label);
});
label is a string value you can pass to update
as an option.
export const counterStore = new Store<CounterState>({ initialValue: 1 });
counterStore.update(value => value + 1, { label: 'increment' });
Reset the store: .reset(): void
reset
the store with the initial value.
export const counterStore = new Store<CounterState>({ initialValue: 1 });
counterStore.reset();
Integration Example
Use with immer
immer is a library to work with immutable state in a more convenient way.
You can use immer intuitively with Store#update
.
import { Store } from '@lacolaco/reactive-store';
import produce from 'immer';
const store = new Store({
initialValue: { count: 0 },
});
store.update(
produce(draft => {
draft.count = 5; // mutate draft directly
}),
);
console.log(store.value); // => 5
Use in Angular
In Angular application, I recommend to creating new store with extending Store
and provide it for Dependency Injection.
// state/counter.store.ts
import { Injectable } from '@angular/core';
import { Store } from '@lacolaco/reactive-store';
export interface CounterState {
count: number;
}
export const initialValue: CounterState = {
count: 0,
};
// Or you can use your NgModule's `providers` array to provide this service.
@Injectable({ providedIn: 'root' })
export class CounterStore extends Store<CounterState> {
constructor() {
super({ initialValue });
}
}
// app.component.ts
@Component({
selector: 'app-root',
template: `
<p>Counter: {{ count$ | async }}</p>
`,
})
export class AppComponent implements OnInit {
count$: Observable<number>;
constructor(private counterStore: CounterStore) {
this.count$ = this.counterStore.select(value => value.count);
}
incrementCount() {
this.counterStore.update(
value => ({
...value,
count: value.count + 1,
}),
{ label: 'increment' },
);
}
}
License
MIT
Author
Suguru Inatomi a.k.a. @lacolaco