Reputation: 1109
I want to return an Observable of one object, selected from an array of objects from my store. I would like to use the .single operator, because this would throw an exception if there are less or more than 1 objects present. I can't get it to work though, because my store is returning an array instead of an observable. I know I could use .filter on the array, but I would like to understand why I can't get it to work. I even tried using a selector.
My App reducer has:
export interface AppState {
batStores: fromBatStores.State;
}
export const reducers: ActionReducerMap<AppState> = {
batStores: fromBatStores.batStoresReducer,
};
The reducer of my module:
export interface State {
batStores: BatStore[];
}
const initialState = {
batStores: [
new BatStore(0, 0, true, 'Cell 11', 11, 11),
new BatStore(1, 0, false, 'Cel 12', 12, 11),
new BatStore(2, 0, true, 'Cel 13', 13, 11),
new BatStore(2, 0, true, 'Cel 14', 14, 11),
]
};
export const selectBatStores = (state: fromApp.AppState) => state.batStores;
export const selectBatStoresBatStores = createSelector(selectBatStores, (state: State) => state.batStores);
Now I try something like this in my ngOnInit:
ngOnInit() {
const editId = 11;
const batStore$: Observable<BatStore> = this.store.select(fromBatStores.selectBatStoresBatStores)
.single(bs => bs.number === editId);
this.batStore$ = batStore$;
}
PhpStorm says the number property doesn't exist on BatStore[]. Which I don't understand, because at the example documentation single is also used as an operator on observable array.
After reading the answers, still very confused. To test, I wrote my function like this:
getBatStore(id: number) {
const versionA$ = this.store.select(fromBatStores.selectBatStoresBatStores)
.switchMap((batStores: BatStore[]) => Observable.from(batStores))
.filter((bs: BatStore) => {
console.log('Analyzing ', bs);
console.log('Result ', bs.number === id);
return bs.number === id;
});
const versionB$ = this.store.select(fromBatStores.selectBatStoresBatStores)
.switchMap((batStores: BatStore[]) => Observable.from(batStores))
.single((bs: BatStore) => {
console.log('Analyzing ', bs);
console.log('Result ', bs.number === id);
return bs.number === id;
});
console.log('VersionA: ', versionA$);
console.log('VersionB: ', versionB$);
console.log('Same? ', versionA$ === versionB$);
return versionB$;
}
Both versions output the same thing to the console. VersionA works when using with async pipe in my view, VersionB doesn't. The code compiles and PhpStorm doesn't complain.
Upvotes: 2
Views: 3346
Reputation: 2638
I understand your confusion, PhpStorm is right to say "the number property doesn't exist on BatStore[]". The example documentation is also correct. The key here, in this example from RxJs, is you need to know how the Observable.from([array])
works. An Observable.from([array])
creates a "cold" observable that does one emission for every item in the array. So, the resulting Observable
is of type number
, not of type array
, as you can see here:
const source: Observable<number> = Rx.Observable.from([1,2,3,4,5]);
The Observable
that are you facing in your store, is an Observable
of type Array
. For example if you use the operator Observable.of
, this will emit the array, not every item of the array:
const source: Observable<number[]> = Rx.Observable.of([1,2,3,4,5]);
Said that, I would do an Array.filter
, for me it's better than any other transformation on the Observable
stream.
Let me know if you have any doubt, Hope this helps.
Upvotes: 1
Reputation: 28434
The select method will retorn an array object, for that reason you cant access the number property. You could try the following:
ngOnInit() {
const editId = 11;
this.batStore$ = this.store.select(fromBatStores.selectBatStoresBatStores)
.flatMap(list => list)// flat list to single elements, emit 1 value per element
.single(bs => bs.number === editId);
}
Another approach would be to store the elements in your state as key,value pairs and then either create a selector that takes the Id argument, or store that Id in the state and then compose 2 selectors (1 for collection of elements, 1 for Id) into one.
Upvotes: 0
Reputation: 14221
The select is getting a reference to the whole array and so the number property does not exist on it. Try a switchMap
to switch to a new observable that expands the list so they can be selected one at a time like so:
const batStore$: Observable<BatStore> = this.store.select(fromBatStores.selectBatStoresBatStores)
.switchMap((batStores : BatStore[]) => Observable.from(batStores))
.single((bs: BatStore) => bs.number === editId);
Upvotes: 1