5

我正在 Nx 工作区中使用 Angular 5 和 @ngrx/store 开发企业应用程序。

我们的应用程序的要求之一是支持打开多个选项卡——这意味着从我们的侧菜单中,用户可以选择多个功能或相同的功能,并在我们的应用程序的单独选项卡中打开它们。我们需要能够分离这些选项卡的状态,因为用户可以在应该完全分开的多个选项卡中打开相同类型的搜索。例如,他们可以打开一个帐户搜索选项卡并按帐户 ID 123456 进行搜索,然后在另一个帐户搜索选项卡上搜索 234567。显然我们不希望一个选项卡中的状态与另一个选项卡发生冲突。

我不确定最好的方法是什么。我们的帐户搜索子功能的示例状态类似于

libs/accounts/src/search/+state/search.reducer.ts

export interface AccountSearchState {
  accounts: { [id: string]: Account };
  metadata: Metadata;
  ids: string[];
  loading: boolean;
  query: AccountQuery;
  ...
}

它上面有一个顶级功能状态,例如:

libs/accounts/src/+state/index.ts

export interface AccountState {
  search: fromSearch.AccountSearchState
  ...
}

我能想到的一种解决方案是将上述子功能状态存储在选项卡 ID 下。

libs/accounts/src/search/+state/search.reducer.ts

export interface AccountSearchState {
  states: { [id: string]: SubState }
}

libs/accounts/src/+state/index.ts

export interface AccountState {
  search: fromSearch.AccountSearchState
  tab: fromTabs.TabState
  ...
}

.. 这将需要一个单独的 TabState 可以在我们所有的功能模块中使用,并且至少包含以下内容:

libs/tabs/src/+state/index.ts

export interface TabState {
  selectedId: string;
}

然后我们可以创建使用这两种状态的选择器:

libs/accounts/src/+state/index.ts

export const getAccountState = createFeatureSelector<AccountState>('account');

export const getTabState = createSelector(getAccountState, (state: AccountState) => state.tab);

export const getAccountSearchState = createSelector(getAccountState, (state: AccountState) => state.search);

export const getAccounts = createSelector(getTabState, getAccountSearchState, (tab, search) => search.states[tab.selectedId].accounts);

当用户单击不同的选项卡时,我们必须发出一个会更改所选选项卡 ID 的事件,这需要通过让每个功能中的选项卡缩减器查找操作并更新功能状态来处理,以便它知道当前的选项卡。这意味着对于我们所有的功能,我们都需要有这种结构——当我真的想以某种方式抽象出来的时候。我只是不确定如何做到这一点。我也不想将选定的选项卡 ID 存储在每个功能存储中 - 相反它将处于根状态,但我不完全确定如何将该值传递给功能状态以返回正确的数据。

任何人都有更好的方法来解决这个问题?

谢谢,

4

3 回答 3

1

我的想法是让选项卡组件为其后代提供自己的存储切片,以便选项卡中的组件可以注入该切片,而无需关心是否存在其他选项卡。

我习惯于使用ngrx/storevia ng-app-state(我写的),所以我的示例代码将使用它,但它只是一个包装器,ngrx/store所以你应该能够在没有它的情况下使用相同的概念。

@Injectable()
export class TabStore extends AppStore<TabState> implements OnDestroy {
  constructor(store: Store<any>) {
    super(store, uniqueId('tabState'), new TabState());
  }

  ngOnDestroy() {
    this.delete(); // when the tab is destroyed, clean up the store
  }
}

@Component({providers: [TabStore]})
export class TabComponent {
  constructor(tabStore: TabStore) {
    // tabStore holds state that is unique to me
  }
}

@Component()
export class ChildOfTabComponent {
  constructor(tabStore: TabStore) {
    // I can also access the same store from descendants
  }
}

uniqueId只有一种方法可以为每个选项卡的状态生成唯一的根级密钥。它在underscore, lodash, 和我的情况下micro-dash- 但任何为每个选项卡生成唯一键的方法都可以。

于 2017-12-11T01:08:58.443 回答
0

可能不完全适用,但我能够通过以下方式在 React Redux 中实现类似的事情。我创建了一个函数工厂,它将reducer 的名称作为参数,并将名称附加到该reducer 的操作类型中。我必须使用工厂的原因是由于某种原因(与引用变量的方式有关),当我刚刚注入一个命名的 reducer 函数时,无论连接到操作类型的键如何,每个函数都会更新。

export const genReducer = (key) => {
    return (state = initialState, action) => {
        switch(action.type) {

            /* Append the key to the type */
            case ActionTypes.SOME_TYPE.concat(key):
                /* do something */

            default:
            return state;
        }
    };
};

然后,每当创建一个新选项卡时,我都会为其分配一个特定于域的名称并将其注入根减速器:

export const rootReducer = (dynamicReducers) => {
    if (dynamicReducers !== undefined) {
        return combineReducers({
            staticReducer1,
            staticReducer2,
            ...dynamicReducers,
        });
    }
    return combineReducers({
        staticReducer1,
        staticReducer2,
    }); 
}

export const injectReducer = (currentStore, { key, reducer }) => {
    if (Object.hasOwnProperty.call(currentStore.dynamicReducers, key)) {
        return;
    }
    currentStore.dynamicReducers[key] = reducer;
    currentStore.replaceReducer(rootReducer(currentStore.dynamicReducers));
};


/* Calling the function */
injectReducer(store, { key: name, reducer: genReducer(name) });

在根 reducer 中,我将键存储在映射中,然后通过选项卡中的任何组件向下传播键。然后每当我打电话给动作创建者时,我都会传递那个键。

export const setValue = (value, key) => {
    return {
        type: types.SET_VALUE.concat(key),
        value,
    }
}

这对我有用,但现在我必须在 Angular 6 中使用适当的选择器来完成。这就是为什么我现在在这个页面上试图弄清楚的原因!希望这是有用的。

于 2018-05-24T20:59:43.023 回答
0

就个人而言,我正在使用Entity Adapter。要记住的唯一非常重要的事情是小心选择器:您需要一个工厂函数以避免多次刷新。

这是一个聪明而天真的方法的例子:ngrx-adapter-multi-components

于 2018-06-26T14:13:55.490 回答