I have a number of reset actions I need to fire at once. Currently I'm using a mergeMap to call the action types as below. However, I think there is a way to replace the type objects with an array of strings but I can't seem to figure it out. Can anyone help?
const resetModuleEpic = action$ =>
action$.pipe(
ofType('RESET_MODULE'),
mergeMap(() =>
of(
{
type: 'RESET_IMAGE'
},
{
type: 'RESET_CATEGORY'
},
{
type: 'RESET_FILTERS'
}
)
)
);
I believe something like below is possible but can't get it right:
const resetModuleEpic = action$ =>
action$.pipe(
ofType('RESET_MODULE'),
merge(['RESET_IMAGE','RESET_CATEGORY','RESET_FILTERS'])
);
Your example should work, probably error is somewhere else in you code.
I have added example at stackblitz. I personally prefer from over of, but cannot reason it.
What you have with merge will just take each item in the array and re-emit it so it'll output just pure strings instead of actions.
There are obviously multiple ways you can achieve what you want. For example, you can use combination of of().map() and turn each string into action:
mergeMap(() => of('RESET_IMAGE','RESET_CATEGORY','RESET_FILTERS').pipe(
map(type => ({ type })),
))
Related
I have to create a queue of ajax requests and group the result but i have no clue about to accomplish this.
Let's say i have an array like this:
const actors = [
"Al Pacino",
"Robert De Niro"
];
I have to iterate over it and for each values make an api call:
export const getMovies = action$ =>
action$.pipe(
ofType(LOAD_REQUEST),
// iterate over the array
// make api call with the actor name
// for each result, make a second api call with the id of the actor (get in the response.results.id)
// group the result in an array with all films of all actors of the array passed in the payload
);
Im stuck with switchMap, pipe ... and don't know the correct way to accomplish this.
Edit Tried your solution Valeriy but got this error:
export const getMovies = action$ =>
action$.pipe(
ofType(LOAD_REQUEST),
switchMap(({ payload }) =>
combineLatest(
payload.map(a => {
return ajax
.getJSON(actor(a))
.pipe(map(response => console.log(response)));
})
)
)
);
TypeError: You provided 'function (source) {
return source.lift.call(Object(_observable_from__WEBPACK_IMPORTED_MODULE_2__["from"])([source].concat(observables)), new _observable_combineLatest__WEBPACK_IMPORTED_MODULE_1__["CombineLatestOperator"](project));
}' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
If I understand you correctly, you are trying to achieve something like this:
export const getMovies = action$ => action$.pipe(
ofType(LOAD_REQUEST),
switchMap(() => {
// group the result in an array with all films of all actors of the array passed in the payload
return combineLatest(
// iterate over the array
...actors.map(actorName => {
// make api call with the actor name
return loadActor(actorName).pipe(
// for each result, make a second api call with the id of the actor (get in the response.results.id)
switchMap(response => loadActorFilms(response.results.id))
);
})
);
})
);
I've used combineLatest to group multiple observables together.
I have an observable stream set up as below. I have an interval that is polling every two seconds. I then switchMap that to make two dependent API calls (mocked here with 'of's). After, I want to use distinctUntilChanged to make sure the final object is different. The only thing is that distinctUntilChanged doesn't fire.
I'm assuming it has SOMETHING to do with the fact that we are creating new streams and therefore never collects two objects to compare, but I don't fully understand.
interval(2000).pipe(
switchMap(() => loadData()),
)
.subscribe(res => console.log(res)); // { name: 'test' } is printed every two seconds
function loadData() {
return of('API call').pipe(
mergeMap(numb => of({ name: 'test' })),
distinctUntilChanged((prev, cur) => {
console.log('CompareFn'); // This will never fire.
return JSON.stringify(prev) === JSON.stringify(cur)})
);
}
Stackblitz: https://stackblitz.com/edit/rxjs-ko6k3c?devtoolsheight=60
In this case, I would like there to only be a single value ever printed from the next handler as distinctUntilChanged should stop all values after the first.
Would appreciate an explanation as to why this isn't working as I would expect it to.
the problem is that your distinctUntilChanged is operating on the inner observable, not the outter... you need to do this
interval(2000).pipe(
switchMap(_ => loadData()),
distinctUntilChanged((prev, cur) => {
console.log('CompareFn');
return JSON.stringify(prev) === JSON.stringify(cur);
})
)
.subscribe(res => console.log(res));
function loadData() {
return of('API call').pipe(
mergeMap(numb => of({ name: 'test' }))
);
}
in your prior set up, only one value ever reached distinctUntilChanged as the interval switched into a new observable via switch map.
I'm trying to make multiple http requests and get returned data in one object.
const pagesToFetch = [2,3]
const request$ = forkJoin(
from(pagesToFetch)
.pipe(
mergeMap(page => this.mockRemoteData(page)),
)
)
mockRemoteData() return a simple Promise.
After first Observable emits (the once created from first entry of pagesToFetch the request$ is completed, second value in not included. How can I fix this?
You can turn each value in pagesToFetch into an Observable and then wait until all of them complete:
const observables = pagesToFetch.map(page => this.mockRemoteData(page));
forkJoin(observables)
.subscribe(...);
Or in case it's not that simple and you need pagesToFetch to be an Observable to collect urls first you could use for example this:
from(pagesToFetch)
.pipe(
toArray(),
mergeMap(pages => {
const observables = pages.map(page => this.mockRemoteData(page));
return forkJoin(observables);
}),
)
.subscribe(...);
Try the below sample format...
Observable.forkJoin(
URL 1,
URL 2
).subscribe((responses) => {
console.log(responses[0]);
console.log(responses[1]);
},
error => {console.log(error)}
);
My scenario is a classic web page with a search form and a result list. I want to encapsulate the behavior of loading the results in an Observable.
Here's what I'm currently doing in TypeScript:
function loadResults(query): Observable<T[]> {}
const results = new Subject<ResultEvent<T[]>>();
const results: Observable<ResultEvent<T[]>> =
form.valueChanges
.distinctUntilChanged()
.do(() => results.next(ResultEvent.pending()))
.switchMap(query => loadResults(query))
.subscribe({
next: (data: T[]) => results.next(ResultEvent.present(data)),
error: err => results.next(ResultEvent.failed(err)),
});
The idea is that results always contains the current state of the search: either pending, present or failed. When the query changes, the result is set to pending, and when the service returns data, the result is set to present.
What I don't like about this solution is the explicit call to subscribe(). I'd rather have a simple Observable that can be subscribed an unsubscribed from (eg. in Angular with the async pipe), without creating an explicit subscribtion. The side-effects in do also seem rather hacky.
const results: Obserbable<ResultEvent<T[]>> =
form.valueChanges.distinctUntilChanged()
. /* here be dragons */;
Thanks for any advice and ideas!
I think you want something along these lines:
const results$ = form.valueChanges
// This is up to you, but with user input it might make sense to
// give it just a little bit of time before we hit the server since
// most user input will be more than a single character.
//.debounceTime(100)
.distinctUntilChanged()
// Using switchMap guarantees that the inner observable will be
// cancelled if the input changed while we are still waiting for
// a result. Newer is always better!
.switchMap(query => loadResults(query)
// If we get data, we use it.
.map(results => ResultEvent.present(results))
// We catch errors and turn them into a failure event.
.catch(err => Observable.of(ResultEvent.failed(err)))
// Whatever happens, first things first.
.startWith(ResultEvent.pending())
);
I would also think about adding a debounceTime in there, by the way.
Here's a snippet you can copy-paste into https://rxviz.com to see it in action (unfortunately their share link feature doesn't work anymore). Make sure to set the time window to something like 10 seconds.
const ResultEvent = {
pending: () => 'Pending',
failed: err => 'Error: ' + err,
present: data => 'Data: ' + data,
};
const loadResults = query => query === 2
? Rx.Observable.of(null).delay(500).switchMap(() => Rx.Observable.throw('Oops'))
: Rx.Observable.of(42).delay(500)
const input$ = Rx.Observable.timer(0, 2000).take(4);
input$.switchMap(query => loadResults(query)
.map(data => ResultEvent.present(data))
.catch(err => Rx.Observable.of(ResultEvent.failed(err)))
.startWith(ResultEvent.pending())
)
I'm new to rxjs and using redux-observable. The short of it is that I need to make a couple promise requests when i get a connection then output the results. I'm wondering if there is a way to join this into a single map at the end and not have to call store.dispatch multiple times and have the retry work for each individual read. Thanks ahead of time for your comments.
export const handleBleConnectionSuccess = (action$,store,{bleCommunicator}) =>
action$.ofType(c.BLE_CONNECTION_SUCCESS)
.do((a)=>{
Observable.fromPromise(bleCommunicator.readCharacteristic(a.device.id,gattInfo.uuid,gattInfo.firmwareRevision.uuid))
.do((value)=>store.dispatch({type:c.DEVICE_FIRMWARE_VERSION,device:{...a.device,firmwareVersion:value}}))
.retry(3);
Observable.fromPromise(bleCommunicator.readCharacteristic(a.device.id,gattInfo.uuid,gattInfo.modelNumber.uuid))
.do(value=>store.dispatch({type:c.DEVICE_MODEL_NUMBER,device:{...a.device,modelNumber:value}}))
.retry(3);
})
.mapTo({type:'DEVICE_INFORMATION_REQUESTED'});
I'm wondering if there is a way to join this into a single map at the end and not have to call store.dispatch multiple times and have the retry work for each individual read
Yes, there is a better way, and it's possible to do what you want.
From the syntax, I'm guessing that you use ngrx (effects) (and not redux-observable).
So with ngrx/effects you could do it like that:
export const handleBleConnectionSuccess = (
action$,
store,
{ bleCommunicator }
) =>
action$.ofType(c.BLE_CONNECTION_SUCCESS).switchMap(a => {
const readCharacteristic = deviceOrFirmwareUuid =>
bleCommunicator.readCharacteristic(a.device.id, gattInfo.uuid, deviceOrFirmwareUuid);
return Observable.merge(
readCharacteristic(gattInfo.firmwareRevision.uuid)
.map(value => ({
type: c.DEVICE_FIRMWARE_VERSION,
device: { ...a.device, firmwareVersion: value },
}))
.retry(3),
readCharacteristic(gattInfo.modelNumber.uuid)
.map(value => ({
type: c.DEVICE_MODEL_NUMBER,
device: { ...a.device, modelNumber: value },
}))
.retry(3),
{ type: 'DEVICE_INFORMATION_REQUESTED' }
);
});