如何通过ID与@ngrx / store存储同一州的多个版本?

时间:2017-12-01 18:45:52

标签: angular tabs ngrx-store ngrx-store-4.0

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

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

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

库/帐户/ SRC /搜索/ +状态/ search.reducer.ts

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

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

库/帐户/ SRC / +状态/ index.ts

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

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

库/帐户/ SRC /搜索/ +状态/ search.reducer.ts

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

库/帐户/ SRC / +状态/ index.ts

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

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

库/选项卡/ SRC / +状态/ index.ts

export interface TabState {
  selectedId: string;
}

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

库/帐户/ SRC / +状态/ 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 - 而是它将处于根状态,但我不完全确定如何将该值传递给功能状态以返回正确的数据

任何人都有任何指示更好地解决这个问题吗?

谢谢,

3 个答案:

答案 0 :(得分:1)

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

我以前通过ng-app-state(我写过)与ngrx/store合作,所以我的示例代码会使用它,但它只是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只有一种方法可以为每个标签的状态生成唯一的根级别密钥。它在underscorelodash,在我的情况下micro-dash - 但是每个标签生成唯一键的任何方法都可以。

答案 1 :(得分: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;
        }
    };
};

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

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) });

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

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

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

答案 2 :(得分:0)

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

这是一个巧妙而幼稚的示例:ngrx-adapter-multi-components