README
Installation
Install the package:
npm i @ngry/store
Optionally, install @ngry/rx
for useful operators like ofType
and toTask
and handy testing tools for Observables:
npm i @ngry/rx
Usage example
Step 1: Declare the state interface(s):
import { TaskState } from '@ngry/rx';
interface Page<T> {
page: number;
size: number;
content: T[];
}
interface Hero {
id: number;
nickname: string;
}
interface HeroListState {
readonly page: number;
readonly pageSize: number;
readonly previewId?: number;
readonly removing: TaskState<boolean>;
}
Step 2: Create custom store by extending Store<TState>
class:
import { Observable, of } from 'rxjs';
import { delay, map, switchMap, tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { MemoryStateContainer, Store } from '@ngry/store';
import { TaskState, toTask } from '@ngry/rx';
@Injectable()
class HeroListStore extends Store<HeroListState> {
readonly previewId$ = this.select(state => state.previewId);
readonly removing$ = this.select(state => state.removing);
readonly page$ = this.select(state => state.page);
readonly pageSize$ = this.select(state => state.pageSize);
readonly offset$ = this.select(
this.page$,
this.pageSize$,
(page: number, size: number) => (page - 1) * size,
);
readonly data$: Observable<TaskState<Page<Hero>>> = this.produce(
this.page$,
this.pageSize$,
args$ => args$.pipe(
switchMap(([page, size]) => this.service.getList(page, size).pipe(
toTask(),
)),
),
);
readonly preview$: Observable<TaskState<Hero>> = this.produce(
this.previewId$,
(args$: Observable<[previewId: number | undefined]>) => args$.pipe(
switchMap(([previewId]) => {
if (previewId != null) {
return this.service.getById(previewId).pipe(
toTask(),
);
} else {
return of(TaskState.initial<Hero>());
}
}),
),
);
readonly removed$: Observable<boolean> = this.produce(
state$ => state$.pipe(
map(state => state.removing.complete && (state.removing.result ?? false)),
),
);
constructor(private service: HeroService) {
super(new MemoryStateContainer({
page: 1,
pageSize: 20,
removing: TaskState.initial(),
}));
}
readonly remove = this.method<[id: number]>(args$ => args$.pipe(
switchMap(([state, id]) => this.service.remove(id).pipe(
toTask(),
tap(removing => this.setState({...state, removing})),
)),
));
readonly removeAll = this.method(args$ => args$.pipe(
switchMap(() => this.service.removeAll().pipe(
toTask(),
tap(removing => this.patchState({removing})),
)),
));
setPage(page: number): void {
this.patchState({page});
}
setPageSize(pageSize: number): void {
this.patchState(() => ({pageSize}));
}
setPageBy(delta: number): void {
this.patchState(state => ({page: state.page + delta}));
}
nextPage(): void {
this.setState(state => ({
...state,
page: state.page + 1,
}));
}
setPreviewId(previewId: number | undefined): void {
this.patchState({previewId});
}
reset(): void {
this.resetState();
}
}
Step 3: Use your new store wherever needed, for example in a component:
import { Component } from '@angular/core';
@Component({
template: '...',
providers: [
HeroListStore,
],
})
class HeroListComponent {
constructor(
readonly store: HeroListStore,
) {
}
}
Step 4: Test your new store:
import { ObservableSpy, TaskState } from '@ngry/rx';
import { TestBed } from '@angular/core/testing';
describe('HeroListStore', () => {
let store: HeroListStore;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
HeroListStore,
],
});
store = TestBed.inject(HeroListStore);
});
describe('#offset