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

标签 angular typescript ngrx ngrx-entity

我想这样做:

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

这是我的状态的屏幕截图。我想将这两件事结合起来: enter image description here

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

我有两个独立的特征存储。每个商店使用NGRX/Entity .

  1. 地点
  2. 客户

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

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。 我尝试从此功能中选择的重要数据是: selectedCustomerId

我在此功能中有一个选择器,如下所示:

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

放置模块

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

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

这就是我的地点状态:

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

我有一个适用于所有地点的选择器:

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

我感兴趣的地点选择器功能是 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 行中抛出。

Initial Error

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

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'.

Inverted Parameters

说明

下面是 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.

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

因此,在您的情况下,您正在传递两个不同的 State , State来自places具有“[placesFeatureKey]”和 State来自customers其中有一个不兼容的“[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

关于angular - 如何创建组合来自两个不同功能模块的数据的 NGRX 选择器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66058838/

相关文章:

css - 如何使用自定义 css 样式覆盖 ng2-bootstrap Accordion 默认样式

html - 头部如何向左显示

javascript - ANTLR4 JavaScript Target非常慢

typescript - 如何解决 react native 屏幕 typescript 中未定义的route.params

大型项目列表的 Angular 2 性能优化

angular - 带有 ngrx/effects 的无限循环

javascript - 删除 Angular 2 + 中所有出现的类

javascript - Angular - ngFor 具有数组属性的对象集合

node.js - 找不到模块 'angular2/angular2'

Angular, Central (Redux )'Store' vs Singleton Service, aka "what happened to separation of concerns"?