该模块有一个根组件(搜索容器),它呈现整个子组件树 – 它们组成了搜索UI和功能,它具有复杂的状态模型和大量的操作和缩减器.
有强烈的要求说:
>功能模块应相互隔离导入,
根据消费者应用程序的要求.
>同一个功能的多个实例应该在同一个父级内共存(例如,具有单独上下文的单独选项卡)
>实例不应具有共享的内部状态,但它们应该能够对全局状态中的相同更改做出反应.
所以我的问题是:
我怎样才能有多个< search-container>< / search-container>一起确保它们独立运作?例如,我想在窗口小部件的一个实例中调度搜索操作,而不是在所有窗口小部件中看到相同的搜索结果.
任何建议都非常感谢.谢谢!
重申您的要求只是为了确保我正确理解它们:
>您有一个模块“搜索”具有自己的组件/状态/减速器/操作等.
>您希望重用该模块以具有许多搜索选项卡,这些选项卡的外观和行为都相同
解决方案:利用行动的元数据
通过操作,存在元数据的概念.基本上,除了payload-Property之外,您还在操作对象的顶层有一个元属性.这与“具有相同的动作,但在不同的环境中”的概念很好地配合.然后,元数据属性将为“id”(如果需要,还有更多内容)来区分要素实例.在根状态中有一个reducer,定义所有动作一次,元数据帮助reducer / effects知道调用哪个“子状态”.
州看起来像这样:
export interface SearchStates { [searchStateId: string]: SearchState; } export interface SearchState { results: string; }
动作如下所示:
export interface SearchMetadata { id: string; } export const search = (params: string,Meta: SearchMetadata) => ({ type: 'SEARCH',payload: params,Meta });
reducer像这样处理它:
export const searchReducer = (state: SearchStates = {},action: any) => { switch (action.type) { case 'SEARCH': const id = action.Meta.id; state = createStateIfDoesntExist(state,id); return { ...state,[id]: { ...state[id],results: action.payload } }; } return state; };
您的模块为root提供一次reducer和可能的效果,对于每个功能(也称为搜索),您提供带有元数据的配置:
// provide this inside your root module @NgModule({ imports: [StoreModule.forFeature('searches',searchReducer)] }) export class SearchModuleForRoot {} // use forFeature to provide this to your search modules @NgModule({ // ... declarations: [SearchContainerComponent] }) export class SearchModule { static forFeature(config: SearchMetadata): ModuleWithProviders { return { ngModule: SearchModule,providers: [{ provide: SEARCH_MetaDATA,useValue: config }] }; } } @Component({ // ... }) export class SearchContainerComponent { constructor(@Inject(SEARCH_MetaDATA) private Meta: SearchMetadata,private store: Store<any>) {} search(params: string) { this.store.dispatch(search(params,this.Meta); } }
如果要隐藏组件中的元数据复杂性,可以将该逻辑移动到服务中,并在组件中使用该服务.在那里,您还可以定义选择器.将服务添加到forFeature中的提供者.
@Injectable() export class SearchService { private selectSearchState = (state: RootState) => state.searches[this.Meta.id] || initialState; private selectSearchResults = createSelector( this.selectSearchState,selectResults ); constructor( @Inject(SEARCH_MetaDATA) private Meta: SearchMetadata,private store: Store<RootState> ) {} getResults$() { return this.store.select(this.selectSearchResults); } search(params: string) { this.store.dispatch(search(params,this.Meta)); } }
在搜索选项卡模块中使用:
@NgModule({ imports: [CommonModule,SearchModule.forFeature({ id: 'searchTab1' })],declarations: [] }) export class SearchTab1Module {} // Now use <search-container></search-container> (once) where you need it
如果您的搜索选项卡看起来完全相同且没有自定义,您甚至可以更改SearchModule以将searchContainer作为路径提供:
export const routes: Route[] = [{path: "",component: SearchContainerComponent}]; @NgModule({ imports: [ RouterModule.forChild(routes) ] // rest stays the same }) export class SearchModule { // ... } // and wire the tab to the root routes: export const rootRoutes: Route[] = [ // ... {path: "searchTab1",loadChildren: "./path/to/searchtab1.module#SearchTab1Module"} ]
然后,当您导航到searchTab1时,将呈现SearchContainerComponent.
…但我想在单个模块中使用多个SearchContainerComponents
您可以在组件级别应用相同的模式:
在SearchService启动时随机创建元数据ID.
在SearchContainerComponent中提供SearchService.
当服务被破坏时,不要忘记清理状态.
@Injectable() export class SearchService implements OnDestroy { private Meta: SearchMetadata = {id: "search-" + Math.random()} // .... } @Component({ // ... providers: [SearchService] }) export class SearchContainerComponent implements OnInit { // ... }
如果您希望ID是确定性的,则必须在某处对它们进行硬编码,然后将它们作为输入传递给SearchContainerComponent,然后使用元数据初始化服务.这当然会使代码更复杂一些.
工作实例