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

时间:2018-03-28 14:43:30

标签: angular ngrx ngrx-store

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

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

有强烈的要求说:

  1. 要素模块应相互隔离导入, 根据消费者应用程序的要求。

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

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

  4. 所以我的问题是:

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

    非常感谢任何建议。谢谢!

1 个答案:

答案 0 :(得分:4)

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

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

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

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

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

状态如下:

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

// 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

相关问题