同一NgRx功能模块的独立实例 [英] Independent instances of the same NgRx feature module

查看:52
本文介绍了同一NgRx功能模块的独立实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用NgRx 5进行Angular 5项目.到目前为止,我已经实现了一个骨架应用程序和一个名为搜索"的功能模块,该功能模块以封装的方式(通过使用forFeature语法).

I am working on an Angular 5 project using NgRx 5. So far I've implemented a skeleton app and a feature module called "Search" which handles its own state, actions and reducers in an encapsulated fashion (by using the forFeature syntax).

该模块具有一个根组件(search-container),该根组件呈现一整个子组件树-它们一起构成了搜索UI和功能,具有复杂的状态模型以及大量的动作和归约器.

This module has one root component (search-container) which renders an entire tree of child components - together they make up the search UI and functionality, which has a complex state model and a good number of actions and reducers.

有很强的要求说:

  1. 功能模块应该彼此隔离地导入, 根据消费者应用的要求.

  1. feature modules should be imported in isolation from each other, as per consumer app's requirements.

同一功能的多个实例应共存于同一父项中(例如,具有单独上下文的单独标签)

multiple instances of the same feature should coexist inside the same parent (e.g. separate tabs with individual contexts)

实例不应具有共享的内部状态,但它们应该能够对全局状态中的相同更改做出反应.

instances shouldn't have a shared internal state but they should be able to react to the same changes in the global state.

所以我的问题是:

如何将多个<search-container></search-container>放在一起,并确保它们独立运行?例如,我想在一个小部件实例内调度搜索动作,而在所有小部件中都看不到相同的搜索结果.

How can I have multiple <search-container></search-container> together and make sure that they function independently? For example, I want to dispatch a search action within one instance of the widget and NOT see the same search results in all of the widgets.

任何建议都将不胜感激.谢谢!

Any suggestions are much appreciated. Thanks!

推荐答案

我遇到了与您类似的问题,并提出了以下解决方案.

I had a similar problem to yours and came up with the following way to solve it.

重申您的要求只是为了确保我正确理解它们:

Reiterating your requirements just to make sure I understand them correctly:

  • 您有一个模块搜索",具有自己的组件/状态/减速器/动作等.
  • 您想重复使用该模块以拥有许多搜索标签,这些标签的外观和行为都相同

有了动作,就有了元数据的概念.基本上,除了有效负载属性之外,您还可以在操作对象的顶层拥有一个元属性.这与具有相同的动作,但在不同的上下文中"的概念很好地配合.然后,元数据属性将为"id"(如果需要,还有更多的东西),以区分功能实例.在根状态内部有一个化简器,一次定义所有动作,元数据可帮助化简器/效果知道调用哪个子状态".

With actions, there is the concept of metadata. Basically, aside from the payload-Property, you also have a meta-property at the top level of your action object. This plays nicely with the concept of "have the same actions, but in different contexts". The metadata property would then be "id" (and more things, if you need them) to differentiate between the feature instances. You have one reducer inside your root state, define all actions once, and the metadata help the reducer/effects to know which "sub-state" is called.

状态如下:

export interface SearchStates {
  [searchStateId: string]: SearchState;
}

export interface SearchState {
  results: string;
}

一个动作看起来像这样:

An action looks like this:

export interface SearchMetadata {
  id: string;
}

export const search = (params: string, meta: SearchMetadata) => ({
  type: 'SEARCH',
  payload: params,
  meta
});

Reducer这样处理:

The reducer handles it like this:

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和可能的效果,并且为每个功能(又名搜索)提供了一个元数据配置:

Your module provides the reducer and possible effects once for root, and for each feature (aka search) you provide a configuration with the metadata:

// 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内的提供程序中.

If you want to hide the metadata complexity from your components, you can move that logic into a service and use that service in your components instead. There you can also define your selectors. Add the service to the providers inside 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));
  }
}

在搜索标签模块中的用法:

Usage inside your search tabs modules:

@NgModule({
  imports: [CommonModule, SearchModule.forFeature({ id: 'searchTab1' })],
  declarations: []
})
export class SearchTab1Module {}
// Now use <search-container></search-container> (once) where you need it

如果您的搜索标签看起来都完全相同,并且没有自定义内容,则您甚至可以更改SearchModule以将searchContainer作为路由提供:

If you your search tabs all look exactly the same and have nothing custom, you could even change SearchModule to provide the searchContainer as a route:

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.

Then, when you navigate to searchTab1, the SearchContainerComponent will be rendered.

您可以在组件级别上应用相同的模式:

You can apply the same pattern but on a component level:

在SearchService启动时随机创建元数据ID.
在SearchContainerComponent内部提供SearchService.
当服务被破坏时,别忘了清理状态.

Create metadata id randomly at startup of SearchService.
Provide SearchService inside SearchContainerComponent.
Don't forget to clean up the state when the service is destroyed.

@Injectable()
export class SearchService implements OnDestroy {
  private meta: SearchMetadata = {id: "search-" + Math.random()}
// ....
}


@Component({
  // ...
  providers: [SearchService]
})
export class SearchContainerComponent implements OnInit {
// ...
}

如果希望ID是确定性的,则必须在某个位置对其进行硬编码,然后例如将其作为输入传递给SearchContainerComponent,然后使用元数据初始化服务.当然,这会使代码更加复杂.

If you want the IDs to be deterministic, you have to hardcode them somewhere, then for example pass them as an input to SearchContainerComponent and then initialize the service with the metadata. This of course makes the code a little more complex.

每个模块: https://stackblitz.com/edit/angular-rs3rt8

每个组件: https://stackblitz.com/edit/angular-iepg5n

这篇关于同一NgRx功能模块的独立实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆