Very simple store implementation for state management with RxJS.
https://yarn.pm/@lacolaco/reactive-store
$ yarn add rxjs @lacolaco/reactive-store
- RxJS: Use the ecosystem
- TypeScript: Type-safe state management
- Simple: Easy to understand what the library does and doesn't
import { Store } from '@lacolaco/reactive-store';
export interface CounterState {
count: number;
}
export const initialValue: CounterState = {
count: 0,
};
export const counterStore = new Store<CounterState>({ initialValue });
export const counterStore = new Store<CounterState>({ initialValue: 1 });
console.log(counterStore.value); // => 1
.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
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
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
});
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' });
Redux Devtools is an useful browser extension for debugging Redux state management. This integration is limited as logging only. jumping, time travelling, or any operation from the extension is not supported.
import { Store, connectReduxDevTools } from '@lacolaco/reactive-store';
const store = new Store({
initialValue: { count: 0 },
});
connectReduxDevTools(store);
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
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' },
);
}
}
MIT
Suguru Inatomi a.k.a. lacolaco
Patreon: https://www.patreon.com/lacolaco