相同 NgRx 功能模块的独立实例
Posted
技术标签:
【中文标题】相同 NgRx 功能模块的独立实例【英文标题】:Independent instances of the same NgRx feature module 【发布时间】:2018-09-07 07:40:20 【问题描述】:我正在使用 NgRx 5 开发一个 Angular 5 项目。到目前为止,我已经实现了一个骨架应用程序和一个名为“搜索”的功能模块,它以封装的方式处理自己的状态、动作和减速器(通过使用 @ 987654321@语法)。
该模块有一个根组件 (search-container
),它呈现一整棵子组件树 - 它们共同构成了搜索 UI 和功能,具有复杂的状态模型以及大量的动作和减速器。
有强烈的要求说:
功能模块应该相互隔离导入, 根据消费者应用程序的要求。
同一功能的多个实例应共存于同一父项中(例如,具有单独上下文的单独选项卡)
实例不应具有共享的内部状态,但它们应该能够对全局状态中的相同更改做出反应。
所以我的问题是:
如何将多个<search-container></search-container>
放在一起并确保它们独立运行?例如,我想在一个小部件实例中调度搜索操作,而不是在所有小部件中看到相同的搜索结果。
非常感谢任何建议。谢谢!
【问题讨论】:
找到解决办法了吗? @ParthGhiya 不幸的是没有。我所做的是在创建时为每个容器分配 ID。因此,一个特性的状态看起来就像一个 id -> containerState 的映射。处理这些会增加很多额外的复杂性,例如为每个容器的子组件集提供正确的 id、调度 id 感知操作、装饰 reducer 以修改容器状态以及使用动态生成的选择器,因为您不能将容器 id 作为参数传递到 ngrx 选择器。我最终围绕容器管理编写了一个完整的元框架:( 【参考方案1】:我遇到了与您类似的问题,并想出了以下方法来解决它。
重申您的要求只是为了确保我正确理解它们:
您有一个“搜索”模块,其中包含自己的组件/状态/reducer/动作等。 您希望重用该模块以拥有许多外观和行为相同的搜索选项卡解决方案:利用动作的元数据
对于动作,有元数据的概念。基本上,除了有效负载属性之外,您在操作对象的顶层还有一个元属性。这与“具有相同的动作,但在不同的上下文中”的概念非常吻合。然后元数据属性将是“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,然后使用元数据初始化服务。这当然会使代码更复杂一些。
工作示例
每个模块: https://stackblitz.com/edit/angular-rs3rt8
每个组件: https://stackblitz.com/edit/angular-iepg5n
【讨论】:
感谢您提供详细的答案 - 这看起来与我最终实现的非常相似,但这里和那里有一些差异,但核心思想是相同的。因此,我很高兴将此答案标记为可行的解决方案。在调度动作并在 reducer 和效果中处理它们时,我已经围绕封装搜索元数据逻辑做了更多的工作,但它仍然感觉像很多样板。我希望在某个时候,ngrx 团队会为这个问题提供更简化的解决方案。 谢谢,这对我在项目中实现虚拟标签很有帮助以上是关于相同 NgRx 功能模块的独立实例的主要内容,如果未能解决你的问题,请参考以下文章
Angular 7、Ngrx、Rxjs 6 - 访问延迟加载模块之间的状态
在 @ngrx/store 4.0 中提供 root reducer