如何创建一个结合来自两个不同功能模块的数据的 NGRX 选择器?

How do I create a NGRX selector that combines data from two different feature modules?

我想这样做:

export const selectPlacesForCurrentCustomer = createSelector(
   selectAllPlaces,
   selectedCustomerId,
   (places: Place, customer_id: string) => places.filter((place) => place.customer === customer_id)
) 

然而,当我这样做时,我得到了这个错误:

Argument of type 'MemoizedSelector<State, string, DefaultProjectorFn>' is not assignable to parameter of type 'Selector<State, string>'.

这是我的应用程序的简化版本,它重现了这个问题。 https://stackblitz.com/edit/angular-10-ngrx-issue?file=src/app/reducers/index.ts

这是我的状态截图。我想结合这两件事:

****************** 背景 ******************

我有两个独立的功能商店。每个商店使用 NGRX/Entity.

  1. 地点
  2. 客户

我想创建一个 selector 从两者中提取数据。这是两个模型的简化界面:

export interface Place {
_id: string;
customer: string;
...
}

export interface Customer {
_id: string;
}

客户模块

客户特征有多个商店。我将此功能导入我的客户模块,如下所示:

 StoreModule.forFeature(fromCustomer.customersFeatureKey, fromCustomer.reducers)

客户功能状态如下所示:


export interface CustomerState {
    [fromCustomers.customerFeatureKey]: fromCustomers.State; //I'm want pull data from here!!
    [fromPaymentMethods.paymentmethodFeatureKey]: fromPaymentMethods.State;
    [fromInvoices.invoiceFeatureKey]: fromInvoices.State;
    [fromSubscriptions.subscriptionFeatureKey]: fromSubscriptions.State;
}

// This is what I pass to the `forFeature` method in my customer module. 
export function reducers(state: CustomerState | undefined, action: Action) {
  return combineReducers({
    [fromCustomers.customerFeatureKey]: fromCustomers.reducer,
    [fromPaymentMethods.paymentmethodFeatureKey]: fromPaymentMethods.reducer,
    [fromInvoices.invoiceFeatureKey]: fromInvoices.reducer,
    [fromSubscriptions.subscriptionFeatureKey]: fromSubscriptions.reducer
  })(state, action);
}

如果我们检查 fromCustomers.State 状态,这就是它的样子:

export interface State extends EntityState<Customer> {
  selectedCustomerId: string | null; // --> This is the first piece of data I'm trying to select.
  loaded: boolean;
  loading: boolean;
  processing: boolean;
}
 
export const adapter: EntityAdapter<Customer> = createEntityAdapter<Customer>({
    selectId: customer => customer._id,
    sortComparer: false
});
 
export const initialState: State = adapter.getInitialState({
    selectedCustomerId: null,
    loaded: false,
    loading: false,
    processing: false
});

...

正如我们在上面看到的,这是我最终设置的 EntityAdapter。 我试图从该功能中 select 的重要数据是: selectedCustomerId.

我有一个 selector 在这个功能中看起来像这样:

export const selectedCustomerId = createSelector(
    selectCustomersEntitiesState,
    (state) => state.selectedCustomerId
)

放置模块

现在我们已经总结了客户模块,让我们继续地点功能。

与客户模块类似,我将其导入 place 模块文件。

这是我的 Place State 的样子:

export interface PlaceState {
    [fromPlaces.placeFeatureKey]: fromPlaces.State;
    //[fromSearch.searchFeatureKey]: fromSearch.State; // This is where you should add search for assignments
}

export interface State extends fromRoot.State {
  [placesFeatureKey]: PlaceState;
}

// This is what I pass to the `forFeature` method in my place module. 
export function reducers(state: PlaceState | undefined, action: Action) {
  return combineReducers({
    [fromPlaces.placeFeatureKey]: fromPlaces.reducer,
  })(state, action);
}

如果我们深入研究 [fromPlaces.placeFeatureKey],您会看到我在哪里为地点模型设置 EntityAdapter。

export interface State extends EntityState<Place> {
  selectedPlaceId: string | null;
  loaded: boolean;
  loading: boolean;
  processing: boolean;
}
 
export const adapter: EntityAdapter<Place> = createEntityAdapter<Place>({
    selectId: place => place._id,
    sortComparer: false
});
 
export const initialState: State = adapter.getInitialState({
    selectedPlaceId: null,
    loaded: false,
    loading: false,
    processing: false
});

我有一个 selector 用于所有地方:

export const {
  selectIds: selectPlaceIds,
  selectEntities: selectPlaceEntities,
  selectAll: selectAllPlaces, // --> This second thing I'm trying to select. 
  selectTotal: selectTotalPlaces,
} = fromPlaces.adapter.getSelectors(selectPlacesEntitiesState);

我感兴趣的 selector from place 特征是 selectAllPlaces

希望你还在我身边。 **背景搭建好了,我想说明一下我希望做什么,但是不知道怎么做。

********** 我想这样做: **********

export const selectPlacesForCurrentCustomer = createSelector(
   selectAllPlaces,
   selectedCustomerId,
   (places: Place, customer_id: string) => places.filter((place) => place.customer === customer_id)
) 

然而,当我这样做时,我得到了这个错误:

Argument of type 'MemoizedSelector<State, string, DefaultProjectorFn>' is not assignable to parameter of type 'Selector<State, string>'.

问题调试

让我们尝试调试抛出错误的代码

export const selectPlacesForCurrentCustomer = createSelector(
  selectAllPlaces,
  selectedCustomerId,
  (customer_id: string, places: Place[]) =>
    places.filter(place => place.customer === customer_id)
);

下面是 IDE 的屏幕截图,表明在 selectedCustomerId

行抛出错误

在错误中我们还注意到下面一行

Property '[customersFeatureKey]' is missing in type 'import("/~/src/app/place/reducers/index").State' but required in type 'import("/~/src/app/customer/reducers/index").State'.

现在让我们尝试反转参数

export const selectPlacesForCurrentCustomer = createSelector(
  selectedCustomerId,
  selectAllPlaces,
  (places: Place[], customer_id: string) =>
    places.filter(place => place.customer === customer_id)
);

现在我们看到错误不再出现在 selectedCustomerId 上,而是出现在 selectAllPlaces 上并且错误已经改变

Property '[placesFeatureKey]' is missing in type 'import("/~/src/app/customer/reducers/index").State' but required in type 'import("/~/src/app/place/reducers/index").State'.

说明

下面是createSelector()

的定义
createSelector(selectors: [Selector<State, S1>, Selector<State, S2>], projector: (s1: S1, s2: S2) => Result): MemoizedSelector<State, Result>

注意点,状态Selector<State, S1>Selector<State, S2>相同的State

下面是文档的摘录,用于解释抛出此错误的原因

The createSelector can be used to select some data from the state based on several slices of the same state.

记下行同一状态的多个切片

因此,在您的情况下,您要传递来自 places 的两个不同 StateState,它们具有“[placesFeatureKey]”和来自 customersState ] 有一个不兼容的“[customersFeatureKey]”

解决方案

选项 1 - 类型转换为同一类型 State


export const selectPlacesForCurrentCustomer = createSelector(
  selectAllPlaces as MemoizedSelector<State, Place[]>,
  selectedCustomerId as MemoizedSelector<State, string>,
  (places: Place[], customer_id: string) =>
    places?.filter(place => place.customer === customer_id)
);

See sample demo of this solution

选项 2 - 在可观察流中使用管道而不是创建选择器

您可以简单地使用管道将组件或服务中的两个流结合起来

  allPlaces$ = this.store.select(selectAllPlaces);
  selectedCustomerId$ = this.store.select(selectedCustomerId);
  selectPlacesForCurrentCustomer$ = combineLatest([
    this.allPlaces$,
    this.selectedCustomerId$
  ]).pipe(
    map(([places, customer_id]) =>
      places.filter(place => place.customer === customer_id)
    )
  );

See sample demo of this solution

选项 3 - 创建一个选择整个状态并提取相关函数的选择器

export const selectPlacesForCurrentCustomer = (state: State) => {
  const places = selectAllPlaces(state as any);
  const customerId = selectedCustomerId(state as any);
  return places.filter(place => place.customer === customerId);
};

See sample demo