diff --git a/docs/guides/basic/store.md b/docs/guides/basic/store.md index 199317c..60cbfc2 100644 --- a/docs/guides/basic/store.md +++ b/docs/guides/basic/store.md @@ -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']); +} +``` + diff --git a/docs/zh-cn/guides/basic/store.md b/docs/zh-cn/guides/basic/store.md index 84627f6..40509bd 100644 --- a/docs/zh-cn/guides/basic/store.md +++ b/docs/zh-cn/guides/basic/store.md @@ -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']); +} +``` diff --git a/packages/store/src/internals/dispatcher.ts b/packages/store/src/internals/dispatcher.ts index 176a246..7476136 100644 --- a/packages/store/src/internals/dispatcher.ts +++ b/packages/store/src/internals/dispatcher.ts @@ -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' @@ -169,7 +157,7 @@ export class InternalDispatcher { } public dispatch(storeId: string, action: ActionMetadata, originActionFn: () => Observable | void) { - const storeInstance = StoreFactory.instance.get(storeId); + const storeInstance = InternalStoreFactory.instance.get(storeId); const dispatchId = `${action.type}-${generateIdWithTime()}`; let returnResult = undefined; const result$ = compose([ @@ -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()); diff --git a/packages/store/src/internals/store-factory.ts b/packages/store/src/internals/internal-store-factory.ts similarity index 69% rename from packages/store/src/internals/store-factory.ts rename to packages/store/src/internals/internal-store-factory.ts index 23d1f63..c2a0ae8 100644 --- a/packages/store/src/internals/store-factory.ts +++ b/packages/store/src/internals/internal-store-factory.ts @@ -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; @@ -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(); diff --git a/packages/store/src/public-api.ts b/packages/store/src/public-api.ts index d0bd18d..cb36734 100644 --- a/packages/store/src/public-api.ts +++ b/packages/store/src/public-api.ts @@ -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'; diff --git a/packages/store/src/store-factory.ts b/packages/store/src/store-factory.ts new file mode 100644 index 0000000..5add70a --- /dev/null +++ b/packages/store/src/store-factory.ts @@ -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); + } +} diff --git a/packages/store/src/store.ts b/packages/store/src/store.ts index 0e7d873..3b0bb93 100644 --- a/packages/store/src/store.ts +++ b/packages/store/src/store.ts @@ -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 @@ -19,14 +19,17 @@ export class Store implements Observer, OnDestroy { private defaultStoreInstanceId: string; + private name: string; + private storeOptions: StoreOptions; constructor(initialState: Partial, options?: StoreOptions) { this.storeOptions = options; + this.name = this.setName(); this.defaultStoreInstanceId = this.createStoreInstanceId(); this.state$ = new BehaviorSubject(initialState as T); this.initialStateCache = { ...initialState } as T; - StoreFactory.instance.register(this); + InternalStoreFactory.instance.register(this); InternalDispatcher.instance.dispatch( this.getStoreInstanceId(), { @@ -142,7 +145,7 @@ export class Store implements Observer, OnDestroy { } ngOnDestroy() { - StoreFactory.instance.unregister(this); + InternalStoreFactory.instance.unregister(this); this.cancelUncompleted(); } @@ -161,19 +164,27 @@ export class Store implements Observer, 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; } diff --git a/packages/store/src/store/examples/active/active.component.ts b/packages/store/src/store/examples/active/active.component.ts index 3e844f7..a4d2045 100644 --- a/packages/store/src/store/examples/active/active.component.ts +++ b/packages/store/src/store/examples/active/active.component.ts @@ -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', diff --git a/packages/store/src/store/examples/active/avtive.store.ts b/packages/store/src/store/examples/active/active.store.ts similarity index 100% rename from packages/store/src/store/examples/active/avtive.store.ts rename to packages/store/src/store/examples/active/active.store.ts diff --git a/packages/store/src/store/examples/todos/todos.store.ts b/packages/store/src/store/examples/todos/todos.store.ts index fa83bcd..733f88f 100644 --- a/packages/store/src/store/examples/todos/todos.store.ts +++ b/packages/store/src/store/examples/todos/todos.store.ts @@ -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'; @@ -19,11 +19,13 @@ let id: number; export class TodosStore extends EntityStore { newTodoText: string; + stores = []; + static todosSelector(state: TodosState) { return state.entities; } - constructor(private todosApiService: TodosApiService) { + constructor(private todosApiService: TodosApiService, private storeFactory: StoreFactoryService) { super( { entities: [], @@ -31,6 +33,7 @@ export class TodosStore extends EntityStore { }, { idKey: 'id' } ); + this.stores = this.storeFactory.getStores(['ItemsStore', 'AnotherItemsStore']); } private getWithCompleted(completed: Boolean) { diff --git a/packages/store/src/test/store.spec.ts b/packages/store/src/test/store.spec.ts index bdf4a5f..2d439df 100644 --- a/packages/store/src/test/store.spec.ts +++ b/packages/store/src/test/store.spec.ts @@ -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; @@ -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(); @@ -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); + }); + }); });