I try to use rxjs rety operator, its work fine with observable:
const obs$: Observable<number> = new Observable((observer) => {
observer.next(1);
observer.complete();
});
obs$
.pipe(
mergeMap(async () => [
await dataService.getAllSomthing(),
await dataService.getAllomthingElse(),
await dataService.getAllSomthingElseElse(),
]),
map(([somthing, somthingElse, somthingElseElse]) => {
dispatch(
enableToasterAction({
text: "good",
type: ToasterType.Success,
})
);
}),
retry(2),
catchError((err) => {
return of(null);
})
)
.subscribe((val: any) => {});
But its not work when I using Subject:
const sub=new Subject();
sub
.pipe(
switchMap(async ({ somthinng}: any) => {
return [await dataService.getSomthing(somthinng)];
}),
map(([somthinngRes]) => {
dispatch(
enableToasterAction({
text: "good",
type: ToasterType.Success,
})
);
}),
retry(2),
catchError((err) => {
return of(null);
})
)
.subscribe((val: any) => {});
sub.next({});
Someone can help me to understand what the difference between them, why its work with observable, but not with subject ?
You can retry an cold observable but not hot observable (subject)
if you want to retry a action trigger by hot observable, you can however move the retry() operator to inner observable. For example
fromEvent(document,'click').pipe(
switchMap(evt=>from(fetch('someurl')).pipe(retry(2))
)
That way the http call triggered by click will retry 2 times when it fails
Subject has an internal state and once it receives complete or error notification it marks itself as isStopped and will never ever emit anything.
So retry() tries to resubscribe but the source Subject will just return Subscription.EMPTY and won't make a real subscribtion.
Related
I have an effect in NgRX effect, as follows:
$createOrganisation = createEffect(() =>
this.actions$.pipe(
ofType(fromOrganisationActions.createOrganisation),
switchMap((data) => this.organisation.createOrganisation(data)),
map((response) => fromOrganisationActions.createOrganisationSuccess({ orgId: response.id })),
catchError((error) => {
return of(fromOrganisationActions.createOrganisationError(error));
})
)
);
However, my stream never seems to end when the catchError is triggered, i.e. in the instance of this.organisation.createOrganisation returning a 400 error.
The action, fromOrganisationActions.createOrganisationError(error) is triggered and my reducer is triggered from this... but if I re-trigger the fromOrganisationActions.createOrganisation effect, this effect runs but the API call is never made a second time.
If I configure it as follows, and dispatch it manually, it works:
$createOrganisation = createEffect(() =>
this.actions$.pipe(
ofType(fromOrganisationActions.createOrganisation),
switchMap((data) => this.organisation.createOrganisation(data)),
map((response) => fromOrganisationActions.createOrganisationSuccess({ orgId: response.id })),
catchError((error) => {
this.store.dispatch(fromOrganisationActions.createOrganisationError(error));
return throwError(error);
})
)
);
But other examples online suggest the first way should work, but it is not for me, and I do not understand why my stream never ends.
Can somebody please advise and tell me why my stream never ends in the first instance?
The catchError should be added to the inner observable.
$createOrganisation = createEffect(() =>
this.actions$.pipe(
ofType(fromOrganisationActions.createOrganisation),
switchMap((data) => this.organisation.createOrganisation(data).pipe (
map((response) =>
fromOrganisationActions.createOrganisationSuccess({ orgId: response.id })),
catchError((error) => {
return of(fromOrganisationActions.createOrganisationError(error));
})
)),
)
);
These mistakes can be caught with eslint-plugin-ngrx, more details about the rule here.
I'm new to rsjx but I'm looking for a solution like the Forkjoin but it should complete when 1 of the 2 observables have a value. It needs to be like a ForkJoin because I need to know which observable got a value.
example:
I'm loading categories and I have an autocomplete. Categories is an observable and the valueChanges is returned as an observable.
#Select(IngredientCategoryState.selectIngredientCategories) ingredientCategories$!: Observable<IngredientCategory[]>;
this.filter = this.ingredientForm.controls['category'].valueChanges.pipe(
map((data) => {
if (typeof data === 'object') {
return data.name;
}
return data;
})
)
Then I used the forkJoin but then it requires both observables to have a value
this.filteredIngredientCategories$ = forkJoin({
ingredientCategories: this.ingredientCategories$,
filter: this.filter
}).pipe(
map(({ ingredientCategories, filter }) => {
return this._filter(ingredientCategories, filter);
})
);
UPDATE
I solved it with a combineLatest and triggering the autocomplete field
combineLatest([this.ingredientCategories$, this.filter$])
.pipe(
map((data) => {
return this._filter(data[0], data[1]);
})
).subscribe((ingredientCategories) => {
this.filteredIngredientCategories = ingredientCategories;
});
this.ingredientForm.controls['category'].setValue('');
You can use race to use the first source to emit.
I need to know which observable got a value
Instead of "knowing" which one emitted, you can transform the output of each source to return the object shape you need:
this.filteredIngredientCategories$ = race(
this.ingredientCategories$.pipe(
map(ingredientCategories => ({ ingredientCategories, filter: undefined }))
),
this.filter.pipe(
map(filter => ({ filter, ingredientCategories: undefined }))
)
).pipe(
map(({ ingredientCategories, filter }) => {
return this._filter(ingredientCategories, filter);
})
);
Here's a working StackBlitz demo.
I have the following stream:
const source = fromEvent(document.querySelector('h1'), 'click').pipe(
switchMap(() => {
return timer(500).pipe(
switchMap(() => timer(500).pipe(tap(() => {
throw new Error('error')
})))
)
})
)
When the inner stream throws, the fromEvent source is also stopped. How can I prevent this and keep the source stream alive?
I think you could try the following:
const source = fromEvent(document.querySelector('h1'), 'click').pipe(
switchMap(() => {
return timer(500).pipe(
switchMap(() => timer(500).pipe(tap(() => {
throw new Error('error')
}))),
catchError(() => EMPTY)
)
})
)
where EMPTY is nothing but this:
export const EMPTY = new Observable<never>(subscriber => subscriber.complete());
The reason this is happens is that when an error occurs, even in a next callback(e.g tap's first argument), it will be passed through as:
this.destination.error(caughtError);
where destination is the next subscriber in the chain(downwards). Eventually catchError will be reached and this prevents the error from affecting the outermost stream(whose source is fromEvent).
You can try with retry operator after the first switchMap, like this
const source = fromEvent(document.querySelector('h1'), 'click').pipe(
switchMap(() => {
return timer(500).pipe(
switchMap(() => timer(500).pipe(tap(() => {
throw new Error('error')
})))
)
}),
retry()
)
If no parameter is passed to retry, then it will resubscribe to the source observable an infinite amount of times. Otherwise you can pass an integer which represents the number of times it will retry before eventually error.
Here a stackblitz.
Consider the following:
a$ = someObservable$.pipe(
switchMap(data => liveForEver$)
);
a$.subscribe();
a$.unsubscribe();
Now, liveForEver$ as the name suggests is subscribed to by other parts of the code. Could it be that a$ will stay subscribed after a$ is unsubscribed because switchMap returns a 'living' observable?
When an operator is defined, it usually has behavior to unsubscribe to child subscriptions when it is unsubscribed to. If you make a custom operator and fail to do this, then you'll likely create memory leaks. Consider the following custom operator:
function timesTwo(input$: Observable<number>): Observable<number> {
return new Observable<number>(observer => {
input$.subscribe({
next: val => observer.next(val * 2),
complete: () => observer.complete(),
error: err => observer.error()
});
return {
// I should $input.unsubscribe()
unsubscribe: () => {/*Do Nothing*/}
}
});
}
function timesTwoPipeable<T>(): MonoTypeOperatorFunction<T> {
return input$ => timesTwo(input$);
}
Here I've created my own custom rxjs operator that multiplies a stream of inputs by two. So 1:
const subscription = interval(1000).pipe(map(x => x * 2))
.subscribe(console.log);
setTimeout(() => subscription.unsubscribe(), 5000);
and 2:
const subscription = timesTwo(interval(1000))
.subscribe(console.log);
setTimeout(() => subscription.unsubscribe(), 5000);
and 3:
const subscription = interval(1000).pipe(timesTwoPipeable())
.subscribe(console.log);
setTimeout(() => subscription.unsubscribe(), 5000);
All have identical outputs to the console, but 2 and 3 both subscribe to the interval stream and then do not unsubscribe to it. So the second two quietly create a memory leak. You could test this yourself by changing interval(1000) to interval(1000).pipe(tap(_ => console.log("Still Alive"))) in all three examples.
All the built-in RxJS operators clean up after themselves. If you build your own, be sure to do the same!
Something I noticed in your question is that you tried to unsubscribe to an observable. I'm surprised that didn't create an error.
My inderstanding is that:
a$.subscribe();
a$.unsubscribe();
should be:
const sub = a$.subscribe();
sub.unsubscribe();
Given an observable where you subscribe:
Observable.subscribe(
(res) => {},
(err) => {},
() => {
// do some thing on complete
}
);
Is it a requirement to write (res) and (err)?
Reason being, I'm using Observable.forkJoin([Observable1, Observable2]).subscribe, and I only care about when they are done, and maybe if they yield an error.
To omit the next and error functions, pass undefined for those parameters:
Rx.Observable
.from([0, 1, 2])
.subscribe(
undefined,
undefined,
() => console.log("completed")
);
I would opt to use the .finally() operator instead of omitting the res and err callbacks. This to better convey what it is that you want to happen.
Rx.Observable.forkJoin([obs1, obs2])
.finally(() => console.log('done'))
.subscribe();
Note that it is not required to pass the callbacks to subscribe().
If we are talking of RxJS5 at least, you can also define the specific subscriber methods you want.
Observable.subscribe(
(res) => {},
(err) => {},
() => {
// do some thing on complete
}
);
becomes
Observable.subscribe(
complete: () => {
// do some thing on complete
}
);
As a side note, the other methods that can be explicitly defined are next and error.