如何创建一个结合来自两个不同功能模块的数据的 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.
- 地点
- 客户
我想创建一个 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
的两个不同 State
、State
,它们具有“[placesFeatureKey]”和来自 customers
的 State
] 有一个不兼容的“[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);
};
我想这样做:
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.
- 地点
- 客户
我想创建一个 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
的两个不同 State
、State
,它们具有“[placesFeatureKey]”和来自 customers
的 State
] 有一个不兼容的“[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);
};