首页 文章

相同NgRx功能模块的独立实例

提问于
浏览
8

我正在使用NgRx 5开发一个Angular 5项目 . 到目前为止,我已经实现了一个骨架应用程序和一个名为"Search"的功能模块,它以封装的方式处理自己的状态,动作和缩减器(通过使用 forFeature 语法) .

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

有强烈的要求说:

根据消费者应用程序的要求,

  • 功能模块应相互隔离导入 .

  • 同一特征的多个实例应该在同一个父元素内共存(例如,具有单独上下文的单独选项卡)

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

所以我的问题是:

如何将多个_2627452组合在一起并确保它们独立运行?例如,我想在窗口小部件的一个实例中调度搜索操作,而不是在所有窗口小部件中看到相同的搜索结果 .

任何建议都非常感谢 . 谢谢!

1 回答

  • 3

    我遇到了类似的问题,想出了以下解决方法 .

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

    • 你有一个模块"Search"有自己的组件/状态/减速器/动作等 .

    • 您希望重用该模块以拥有许多搜索选项卡,这些选项卡的外观和行为都相同

    解决方案:利用操作的元数据

    通过行动,存在元数据的概念 . 基本上,除了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,然后使用元数据初始化服务 . 这当然会使代码更复杂一些 .

    工作实例

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

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

相关问题