我遇到了一个与你类似的问题,并想出了以下解决方法。
重申您的要求,以确保我正确理解:
-
您有一个模块“搜索”,其中包含自己的组件/状态/减速机/操作等。
-
您希望重用该模块以拥有许多搜索选项卡,这些选项卡的外观和行为都相同
解决方案:利用动作元数据
对于操作,有元数据的概念。基本上,除了payload属性之外,在动作对象的顶层还有一个meta属性。这很好地体现了“有相同的动作,但在不同的环境中”的概念。然后,元数据属性将是“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
});
减速器处理方式如下:
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提供了一次缩减器和可能的效果,并为每个功能(也称为搜索)提供了元数据配置:
// 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,然后使用元数据初始化服务。这当然会使代码更加复杂。
工作示例
每个模块:
https://stackblitz.com/edit/angular-rs3rt8
每个组件:
https://stackblitz.com/edit/angular-iepg5n