Angular 同一NgRx功能模块的独立实例

Angular 同一NgRx功能模块的独立实例,angular,ngrx,ngrx-store,Angular,Ngrx,Ngrx Store,我正在使用NgRx 5进行Angular 5项目。到目前为止,我已经实现了一个框架应用程序和一个名为“搜索”的功能模块,它以封装的方式(通过使用forFeature语法)处理自己的状态、操作和简化程序 此模块有一个根组件(搜索容器),它呈现整个子组件树-它们一起构成搜索UI和功能,它有一个复杂的状态模型和大量的操作和简化程序 强烈要求如下: 功能模块应相互隔离地导入, 根据消费者应用程序的要求 同一功能的多个实例应在同一父级内共存(例如,具有单独上下文的单独选项卡) 实例不应该具有共享的内部状态

我正在使用NgRx 5进行Angular 5项目。到目前为止,我已经实现了一个框架应用程序和一个名为“搜索”的功能模块,它以封装的方式(通过使用
forFeature
语法)处理自己的状态、操作和简化程序

此模块有一个根组件(
搜索容器
),它呈现整个子组件树-它们一起构成搜索UI和功能,它有一个复杂的状态模型和大量的操作和简化程序

强烈要求如下:

  • 功能模块应相互隔离地导入, 根据消费者应用程序的要求

  • 同一功能的多个实例应在同一父级内共存(例如,具有单独上下文的单独选项卡)

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

  • 所以我的问题是:

    如何将多个
    放在一起并确保它们独立运行?例如,我想在小部件的一个实例中分派一个搜索操作,而不是在所有小部件中看到相同的搜索结果


    任何建议都将不胜感激。谢谢

    我遇到了一个与你类似的问题,并想出了以下解决方法

    重申您的要求,以确保我正确理解:

    • 您有一个模块“搜索”,其中包含自己的组件/状态/减速器/动作等
    • 您希望重用该模块以拥有许多搜索选项卡,这些选项卡的外观和行为都相同
    解决方案:利用操作的元数据 有了动作,就有了元数据的概念。基本上,除了payload属性之外,在动作对象的顶层还有一个元属性。这很好地体现了“有相同的动作,但在不同的环境中”的概念。然后,元数据属性将是“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;
    };
    
    操作如下所示:

    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;
    };
    
    减速器的操作方式如下:

    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);
      }
    }
    
    然后,当导航到searchTab1时,将呈现SearchContainerComponent

    …但我想在单个模块中使用多个SearchContainer组件 您可以在组件级别应用相同的模式:

    在SearchService启动时随机创建元数据id。
    在SearchContainer组件内提供SearchService。
    当服务被破坏时,不要忘记清理状态

    @Injectable()
    export class SearchService implements OnDestroy {
      private meta: SearchMetadata = {id: "search-" + Math.random()}
    // ....
    }
    
    
    @Component({
      // ...
      providers: [SearchService]
    })
    export class SearchContainerComponent implements OnInit {
    // ...
    }
    
    如果希望ID具有确定性,则必须在某处对其进行硬编码,然后将其作为输入传递给SearchContainerComponent,然后使用元数据初始化服务。这当然会使代码更复杂一些

    工作示例 每个模块:

    每个组件:

    找到了解决方法吗?@ParthGhiya不幸没有。我所做的是在创建时为每个容器分配ID。因此,特性的状态看起来像{id->containerState}的映射。处理这些会增加很多额外的复杂性,例如为每个容器的子组件集提供正确的id、分派id感知操作、修饰还原程序以修改容器状态以及使用动态生成的选择器,因为您无法将容器id作为参数传递给ngrx选择器。最后,我围绕容器管理编写了一个完整的元框架:(感谢您提供详细的答案-这看起来与我最终实现的内容非常相似,只是有些不同,但核心思想是相同的。因此,我很高兴将此答案标记为一个可行的解决方案。我在调度操作时封装搜索元数据逻辑,并在Reducer中处理它们方面做了更多的工作和效果,但它仍然感觉像很多样板。我希望在某个时候ngrx团队能够为这个问题提供一个更精简的解决方案。谢谢,这对我在项目中实现虚拟标签有很大帮助