Reputation: 697
I am using @ngrx/effects in my Angular 2 application and I'm having some issues trying to implement an effect that is dependent on a condition based on the state of the application.
In my state, I have a map of Location -> Capabilities, keyed by locationId
(as well as other maps of Location -> *). When ACTION_A
occurs, I only want it proceed once there are capabilities that are up-to-date for the current location. When ACTION_A
is dispatched, the capabilities may already be up-to-date, however, the application could also be in the process of loading new ones.
With the code below, once ACTION_A
is dispatched once, isOutOfDate
is called every time any action is dispatched to the store. So, it seems like the observable is still active, even after the up-to-date filter has been met. However, if getCapabilities
is replaced with getCapabilitiesWorking
, then isOutOfDate
is only called until the filter condition is met each time the ACTION_A
action is dispatched, as I would expect from either implementation.
So, it seems like I am missing something with how combineLatest
or filter
or take
works ...
or perhaps I am approaching this all wrong ...
I would rather be able to use the common function for my 'current location' selectors
Any ideas?
@Effect() route$ = actions$
.ofType(ACTION_A)
.switchMap(() => this._store.let(getCapabilities)
.filter(capabilities => !isOutOfDate(capabilities))
.take(1)
)
.map( do some stuff );
// Selector function that causes excess observable emits
export const getCapabilities =
compose(fromLocations.currentLocationLookup(fromLocations.getCapabilities), fromRoot.getLocations);
// Selector function works as expected
export const getCapabilitiesWorking = function (state$: Observable<AppState>): Observable<Capability> {
return state$
.let(fromRoot.getLocations)
.map(l => l.capabilitiesByLocation[l.currentId]);
};
// fromLocations
// -------------
export function getCapabilities(state$: Observable<LocationsState>): Observable<{ [id: string]: Capability }> {
return state$.map(s => s.capabilitiesByLocation);
}
// Find the value in 'collection' corresponding to the current location
export function currentLocationLookup<T>(
collection: (state$: Observable<LocationsState>) => Observable<{ [id: string]: T }>
): (state$: Observable<LocationsState>) => Observable<T> {
return (state$: Observable<LocationsState>): Observable<T> => {
return Observable.combineLatest(
state$.let(getCurrentLocation),
state$.let(collection),
(locId, map) => map[locId]
);
};
}
EDIT: To clarify, the isOutOfDate
function keeps getting invoked, however the "do some stuff" does not...
Upvotes: 5
Views: 11532
Reputation: 129
withLatestFrom
won't work, since resulted observable will emit the output only when source emits(action dispatched), in accordance with doc you need combineLatest
, so:
@Effect() route$ = actions$
.ofType(ACTION_A)
.combineLatest(this._store.let(getCapabilities))
.filter(([action, capabilities]) =>!isOutOfDate(capabilities))
.map( do some stuff );
Upvotes: 6
Reputation: 81
You might try to replace the filter in the switchMap with skipWhile
So the Effect will be:
@Effect() route$ = actions$
.ofType(ACTION_A)
.switchMap(() => this._store.let(getCapabilities)
.skipWhile(capabilities => !isOutOfDate(capabilities))
.take(1)
)
.map( do some stuff );
This helped me in a similar use case :)
Upvotes: 8
Reputation: 111
Try using withLatestFrom method. Your effect can look like
@Effect() route$ = actions$
.ofType(ACTION_A)
.withLatestFrom(this._store.let(getCapabilities))
.filter(([action, capabilities]) =>!isOutOfDate(capabilities))
.map( do some stuff );
Upvotes: -1