Skip to content

Commit

Permalink
feat: public storeFactory and add getStores (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
Guoxiin authored Nov 16, 2022
1 parent 18606f5 commit fb2bb0b
Show file tree
Hide file tree
Showing 11 changed files with 109 additions and 49 deletions.
13 changes: 12 additions & 1 deletion docs/guides/basic/store.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,15 @@ export class ThyStoreCounterExampleComponent implements OnInit {
注意事项:
- 推荐使用`select`返回需要的`Observable`, 在模版中使用`async`管道订阅使用, 如果模版中多处使用可以使用`as`关键字保存为临时对象
- 一旦在代码中订阅使用, 一定要取消订阅, 推荐`takeUntil`操作符取消订阅
- `selector`推荐统一定义到`Store`的静态函数中, 提高性能的话在最后添加`shareReplay`管道时一个不错的好习惯
- `selector`推荐统一定义到`Store`的静态函数中, 提高性能的话在最后添加`shareReplay`管道是一个不错的好习惯

## getStores
`StoreFactoryService``getStores(names: string | string[])`方法,可以通过`Store`名字,取到注册过的所有`Store`
```ts
import { StoreFactoryService } from '@tethys/store';

constructor(private storeFactory: StoreFactoryService) {
stores = this.storeFactory.getStores(['ItemsStore', 'AnotherItemsStore']);
}
```

12 changes: 11 additions & 1 deletion docs/zh-cn/guides/basic/store.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,14 @@ export class ThyStoreCounterExampleComponent implements OnInit {
注意事项:
- 推荐使用`select`返回需要的`Observable`, 在模版中使用`async`管道订阅使用, 如果模版中多处使用可以使用`as`关键字保存为临时对象
- 一旦在代码中订阅使用, 一定要取消订阅, 推荐`takeUntil`操作符取消订阅
- `selector`推荐统一定义到`Store`的静态函数中, 提高性能的话在最后添加`shareReplay`管道时一个不错的好习惯
- `selector`推荐统一定义到`Store`的静态函数中, 提高性能的话在最后添加`shareReplay`管道是一个不错的好习惯

## getStores
`StoreFactoryService``getStores(names: string | string[])`方法,可以通过`Store`名字,取到注册过的所有`Store`
```ts
import { StoreFactoryService } from '@tethys/store';

constructor(private storeFactory: StoreFactoryService) {
stores = this.storeFactory.getStores(['ItemsStore', 'AnotherItemsStore']);
}
```
28 changes: 8 additions & 20 deletions packages/store/src/internals/dispatcher.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, EMPTY, Observable, of, Subject, throwError } from 'rxjs';
import {
filter,
take,
shareReplay,
exhaustMap,
switchMap,
takeUntil,
defaultIfEmpty,
catchError,
map,
mergeMap,
tap
} from 'rxjs/operators';
import { EMPTY, Observable, of, Subject, throwError } from 'rxjs';
import { catchError, defaultIfEmpty, filter, map, mergeMap, shareReplay, take, takeUntil } from 'rxjs/operators';
import { CancelUncompleted } from '../action';
import { ActionContext, ActionStatus } from '../actions-stream';
import { SafeAny, ActionMetadata } from '../inner-types';
import { PluginContext, StorePluginFn } from '../plugin';
import { ActionMetadata } from '../inner-types';
import { PluginContext } from '../plugin';
import { StorePluginManager } from '../plugin-manager';
import { compose, findAndCreateStoreMetadata, generateIdWithTime } from '../utils';
import { StoreFactory } from './store-factory';
import { compose, generateIdWithTime } from '../utils';
import { InternalStoreFactory } from './internal-store-factory';

@Injectable({
providedIn: 'root'
Expand Down Expand Up @@ -169,7 +157,7 @@ export class InternalDispatcher {
}

public dispatch(storeId: string, action: ActionMetadata, originActionFn: () => Observable<unknown> | void) {
const storeInstance = StoreFactory.instance.get(storeId);
const storeInstance = InternalStoreFactory.instance.get(storeId);
const dispatchId = `${action.type}-${generateIdWithTime()}`;
let returnResult = undefined;
const result$ = compose([
Expand Down Expand Up @@ -211,7 +199,7 @@ export class InternalDispatcher {
])({
state: storeInstance.getState(),
getState: () => storeInstance.getState(),
getAllState: () => StoreFactory.instance.getAllState(),
getAllState: () => InternalStoreFactory.instance.getAllState(),
store: storeInstance,
action: `${storeInstance.getStoreInstanceId()}@${action.type}`
}).pipe(shareReplay());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Injectable, OnDestroy } from '@angular/core';
import { BehaviorSubject, Subject } from 'rxjs';
import { Subject } from 'rxjs';
import { Store } from '../store';
import { coerceArray } from '../utils';

@Injectable()
export class StoreFactory implements OnDestroy {
private static factory = new StoreFactory();
export class InternalStoreFactory implements OnDestroy {
private static factory = new InternalStoreFactory();

static get instance() {
return this.factory;
Expand All @@ -26,6 +27,12 @@ export class StoreFactory implements OnDestroy {
return this.storeInstancesMap.get(id);
}

getStores(names: string | string[]) {
return Array.from(this.storeInstancesMap.values()).filter((store) => {
return coerceArray(names).includes(store.getName());
});
}

getAllState() {
return Array.from(this.storeInstancesMap.entries()).reduce((state, [storeId, store]) => {
state[storeId] = store.getState();
Expand Down
13 changes: 7 additions & 6 deletions packages/store/src/public-api.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export * from './module';
export * from './store';
export * from './entity-store';
export * from './action';
export * from './references';
export * from './plugins/redux-devtools';
export * from './entity-store';
export * from './module';
export * from './plugin';
export { PaginationInfo, Id, StoreOptions } from './types';
export * from './plugins/redux-devtools';
export * from './references';
export * from './store';
export * from './store-factory';
export { Id, PaginationInfo, StoreOptions } from './types';
11 changes: 11 additions & 0 deletions packages/store/src/store-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Injectable } from '@angular/core';
import { InternalStoreFactory } from './internals/internal-store-factory';

@Injectable({ providedIn: 'root' })
export class StoreFactoryService {
constructor() {}

getStores(names: string | string[]) {
return InternalStoreFactory.instance.getStores(names);
}
}
33 changes: 22 additions & 11 deletions packages/store/src/store.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Observable, Observer, BehaviorSubject, from, of, PartialObserver, Subscription } from 'rxjs';
import { Injectable, isDevMode, OnDestroy } from '@angular/core';
import { isFunction, isNumber } from '@tethys/cdk/is';
import { BehaviorSubject, from, Observable, Observer, Subscription } from 'rxjs';
import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators';
import { META_KEY, StoreOptions } from './types';
import { OnDestroy, isDevMode, Injectable } from '@angular/core';
import { Action } from './action';
import { isFunction, isNumber } from '@tethys/cdk/is';
import { StoreFactory } from './internals/store-factory';
import { InternalDispatcher } from './internals/dispatcher';
import { StoreMetaInfo } from './inner-types';
import { InternalDispatcher } from './internals/dispatcher';
import { InternalStoreFactory } from './internals/internal-store-factory';
import { META_KEY, StoreOptions } from './types';

/**
* @dynamic
Expand All @@ -19,14 +19,17 @@ export class Store<T = unknown> implements Observer<T>, OnDestroy {

private defaultStoreInstanceId: string;

private name: string;

private storeOptions: StoreOptions;

constructor(initialState: Partial<T>, options?: StoreOptions) {
this.storeOptions = options;
this.name = this.setName();
this.defaultStoreInstanceId = this.createStoreInstanceId();
this.state$ = new BehaviorSubject<T>(initialState as T);
this.initialStateCache = { ...initialState } as T;
StoreFactory.instance.register(this);
InternalStoreFactory.instance.register(this);
InternalDispatcher.instance.dispatch(
this.getStoreInstanceId(),
{
Expand Down Expand Up @@ -142,7 +145,7 @@ export class Store<T = unknown> implements Observer<T>, OnDestroy {
}

ngOnDestroy() {
StoreFactory.instance.unregister(this);
InternalStoreFactory.instance.unregister(this);
this.cancelUncompleted();
}

Expand All @@ -161,19 +164,27 @@ export class Store<T = unknown> implements Observer<T>, OnDestroy {
return this.defaultStoreInstanceId;
}

getName(): string {
return this.name;
}

private getNameByConstructor() {
return this.constructor.name || /function (.+)\(/.exec(this.constructor + '')[1];
}

private setName(): string {
return (this.storeOptions && this.storeOptions.name) || this.getNameByConstructor();
}

private createStoreInstanceId(): string {
const instanceMaxCount = this.getInstanceMaxCount();
const name = (this.storeOptions && this.storeOptions.name) || this.getNameByConstructor();
if (!StoreFactory.instance.get(name)) {
const name = this.getName();
if (!InternalStoreFactory.instance.get(name)) {
return name;
}
let j = 0;
for (let i = 1; i <= instanceMaxCount - 1; i++) {
if (!StoreFactory.instance.get(`${name}-${i}`)) {
if (!InternalStoreFactory.instance.get(`${name}-${i}`)) {
j = i;
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { ActiveItemsStore } from './avtive.store';
import { ActiveItemsStore } from './active.store';

@Component({
selector: 'thy-store-active-example',
Expand Down
7 changes: 5 additions & 2 deletions packages/store/src/store/examples/todos/todos.store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { Action, EntityState, EntityStore } from '@tethys/store';
import { Action, EntityState, EntityStore, StoreFactoryService } from '@tethys/store';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';

Expand All @@ -19,18 +19,21 @@ let id: number;
export class TodosStore extends EntityStore<TodosState, Todo> {
newTodoText: string;

stores = [];

static todosSelector(state: TodosState) {
return state.entities;
}

constructor(private todosApiService: TodosApiService) {
constructor(private todosApiService: TodosApiService, private storeFactory: StoreFactoryService) {
super(
{
entities: [],
someKey: '1'
},
{ idKey: 'id' }
);
this.stores = this.storeFactory.getStores(['ItemsStore', 'AnotherItemsStore']);
}

private getWithCompleted(completed: Boolean) {
Expand Down
26 changes: 22 additions & 4 deletions packages/store/src/test/store.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Injectable } from '@angular/core';
import { fakeAsync, TestBed } from '@angular/core/testing';
import { produce } from '@tethys/cdk/immutable';
import { of, throwError } from 'rxjs';
import { mergeMap, tap } from 'rxjs/operators';
import { Action } from '../action';
import { Store } from '../store';
import { of, throwError, Observable } from 'rxjs';
import { tap, mergeMap } from 'rxjs/operators';
import { produce } from '@tethys/cdk/immutable';
import { fakeAsync, tick } from '@angular/core/testing';
import { StoreFactoryService } from '../store-factory';

interface Animal {
id: number;
Expand Down Expand Up @@ -328,6 +329,14 @@ describe('#store', () => {
});
});

describe('#getStoreInstanceName', () => {
it('should get correct InstanceName', () => {
store = new ZoomStore({});
expect(store.getName()).toEqual('ZoomStore');
store.ngOnDestroy();
});
});

describe('#error', () => {
it('should throw error', fakeAsync(() => {
store = new ZoomStore();
Expand Down Expand Up @@ -358,4 +367,13 @@ describe('#store', () => {
expect(errorSpy).toHaveBeenCalledWith(new Error(`add animal failed`));
});
});

describe('#storeFactory', () => {
it('should get stores by name', () => {
store = new ZoomStore({});
const storeFactory = TestBed.inject(StoreFactoryService);
const stores = storeFactory.getStores('ZoomStore');
expect(stores[0]).toEqual(store);
});
});
});

0 comments on commit fb2bb0b

Please sign in to comment.