Make takeUntil first trigger the remove handlers of fromEvent first - rxjs

I have a pipeline that ends with takeUntil, the problem is switchMap is getting triggered because the combineLatest is not canceled till the taps inside takeUntil end.
My pseudocode:
combineLatest(
fromEventPattern(handler => {
document.addEventListener('disconnect', handler, false);
console.log('added');
},
handler => {
document.removeEventListener('disconnect', handler, false);
console.log('removed');
}
).pipe(
switchMap(() => {
console.log('SWITCH MAP TRIGGERED!');
return EMPTY;
]),
takeUntil(
mySubject.pipe(
filter(type => type === 'LOGOUT'),
tap(() => console.log('got logout, so do logout calls'),
tap(() => document.dispatchEvent(new CustomEvent('disconnect')))
)
)
)
Trigger the cancelation with:
mySubject.next({ type: 'LOGOUT' });
The problem I'm having is that it's not removing the event listenrs until after the taps in takeUntil finish. Due to this, the dispatch of the "disconnect" event is triggering the switchMap to trigger again. I want things to cancel immediately as the filter in takeUntil is met, and then it should do that final action of dispatchEvent('disconnect').

Related

Can I remove .subscribe() from map by using another RxJS operator?

Is there an alternative to doing .subscribe() in map?
return this.http.patch<any>(URL, { test: true }).pipe(
tap(_ => this.nextSomething$.next({})),
filter(_ => this.filter),
map(resp => {
this.someObservable({ message: 'do' }).subscribe()
return resp
})
)
I tried doing switchMap and returning the previous response but my observable does not complete.
switchMap(prevResp =>
this.someObservable({ message: 'do' }) }).pipe(map( _ => prevResp))
)
Thank you
If your observable is not completing when you switch to using the flattening operator then that means the target observable in that operator isn't completing. So you might want to check what's going on in someObservable that causes it to never complete. It is likey that behavior isn't desirable.
If you know that someObservable will emit at least once, then you can add the first operator to the inner pipe method:
switchMap(prevResp =>
this.someObservable({ message: 'do' }) }).pipe(
first(),
map( _ => prevResp)
)
)
If you don't care what someObservable does - you don't want to wait for it just want it to execute, then wrap the observable in firstValueFrom. It converts an observable into a promise that emits the first result. This code smells, but it should do the trick.
this.http.patch<any>(URL, { test: true }).pipe(
tap(() => this.nextSomething$.next({})),
filter(() => this.filter),
tap(() => firstValueFrom(this.someObservable({ message: 'do' })))
)
Maybe, you don't care to wait for this observable to emit, but you're still looking for an orderly execution of someObservable. In that case you can use a subject that emits every time you want it called, and use concatMap to ensure the execution is performed in an orderly fashion.
private readonly someObservableRequest = new Subject<string>();
constructor() {
this.someObservableRequest.pipe(
concatMap((message) => this.someObservable({ message }))
).subscribe(); // be kind, please unsubscribe
}
someMethod() {
return this.http.patch<any>(URL, { test: true }).pipe(
tap(_ => this.nextSomething$.next({})),
filter(_ => this.filter),
tap(() => this.someObservableRequest('do'))
);
}
If I understand your point right, you have to execute consecutively 2 Observables.
In this case you need yo use one so called "higher order" operators, i.e. either concatMap or switchMap (there are other "higher order" operators but I feel they do not apply to your case).
The code then would look like this
myNewOservable this.http.patch<any>(URL, { test: true }).pipe(
tap(_ => this.nextSomething$.next({})),
filter(_ => this.filter),
// an higher order operator returns an Observable
concatMap(resp => {
return this.someObservable({ message: 'do' })
})
)
return myNewOservable
Now you can subscribe to myNewOservable.
In my example I have used concatMap which ensures that a value notified by the upstream Observable is processed through the downstream pipeline before processing the next value from upstream.
I could have used also switchMap, which has a slightly different behavior: as soon as a new value is notified by the upstream Observable, any downstream processing is terminated.
In this case, given that http emits only one value and then completes the 2 operators act the same.

Reset Rx Js fromEvent observable after subscribe

I have mousemove event:
fromEvent(document, 'mousemove').pipe(
filter(e => e.target !== this.formElement.nativeElement),
filter(_ => this.form.valid),
take(1),
mergeMap((): any => this.confirm()),
).subscribe();
confirm() {
this.snack.open('sure', 'yes').onAction()
.subscribe(_ => {
this.createNoteService.create();
})
}
After confirming the snack button I want the mouse event to start working again.
Is it possible? Thanks!
The reason this only works for you the first time, is because you have take(1) which ends the observable after one value is received.
Also, your confirm() method should return observable; don't subscribe inside the function; mergeMap will automatically subscribe / unsubscribe for you.
However, instead of mergeMap, you can use exhaustMap. This will only allow a single inner subscription at a time. It will ignore any incoming emissions until its current source completes:
fromEvent(document, 'mousemove').pipe(
filter(e => e.target !== formElement.nativeElement),
filter(_ => form.valid),
exhaustMap(() => confirm()),
).subscribe();
function confirm() {
return snack.open('sure', 'yes').onAction().pipe(
tap(() => this.createNoteService.create())
);
}

NGRX concat two action

I am using NGRX and Angular 11.
I am trying to, when an action is executed, call another action, listen to the success of it, and if it succeeded dispatch a final action:
Here my code a bit simplified :
#Effect()
getUserSettingSuccess$ = this.actions$.pipe(
// When this action `GetUserSettingsSuccess` is executed
ofType<featureActions.GetUserSettingsSuccess>(featureActions.ActionTypes.GetUserSettingsSuccess),
concatMap((action) =>
of(action).pipe(withLatestFrom(this.store$.pipe(select(ProjectsStoreSelectors.selectedProjectId))))
),
// I want to dispatch a new action
tap(([action, projectId]) => new ModelsStoreAction.GetAllModelsRequest({ projectId })),
// and listen to the success / failure of that action.
// If success dispatch `SetModelSelection` else do nothing.
map(([action]) =>
this.actions$.pipe(
ofType(ModelsStoreAction.ActionTypes.GetAllModelsSuccess),
takeUntil(this.actions$.pipe(ofType(ModelsStoreAction.ActionTypes.GetAllCesiumModelsFailed))),
first(),
map(() => new ModelsStoreAction.SetModelSelection())
)
)
The problem I have is that, the above code do not dispatch a valid action. But I am getting a bit lost with all those rxjs operator.
Here would be my approach:
#Effect()
getUserSettingSuccess$ = this.actions$.pipe(
// When this action `GetUserSettingsSuccess` is executed
ofType<featureActions.GetUserSettingsSuccess>(featureActions.ActionTypes.GetUserSettingsSuccess),
concatMap((action) =>
of(action).pipe(withLatestFrom(this.store$.pipe(select(ProjectsStoreSelectors.selectedProjectId))))
),
// I want to dispatch a new action
tap(([action, projectId]) => new ModelsStoreAction.GetAllModelsRequest({ projectId })),
// and listen to the success / failure of that action.
// If success dispatch `SetModelSelection` else do nothing.
switchMap(
([action]) => this.actions$.pipe(
// adding all the possibilities here
ofType(ModelsStoreAction.ActionTypes.GetAllModelsSuccess, ModelsStoreAction.ActionTypes.GetAllCesiumModelsFailed),
first(),
filter(a => a.type === ModelsStoreAction.ActionTypes.GetAllModelsSuccess.type),
map(() => new ModelsStoreAction.SetModelSelection()),
)
)
)
It didn't work before because this.actions$ is essentially a type of Subject and an effect is not expected to return an Observable-like value. In order to solve that, I used switchMap, which will handle the inner subscription automatically.

WithLatestFrom($foo) not emitting when $foo emits, possibly because of the use of merge

I am implementing an observable which can be subscribed to before it is "assigned" Think of it like hoisting an observable definition so I dont have to worry about the order in which I create observables derived from other observables, I call it a ColdSubject.
ColdSubject works fine (I can add observables to it, and only when somebody subscribes to the ColdObservable do its operators get evaluated).
However withLatestFrom will never emit while waiting for obs$, despite the observable it's "waiting for" emitting to a subscriber several times!
export class ColdSubject<T> {
// If you subscribe to this before an observable has been added to $acc, you will be notified as soon as one is added, and if you subscribe to this after an observable is added to acc$ you will also be notified
public obs$: Observable<T>;
public acc$ = new BehaviorSubject<Observable<T>>(merge());
constructor() {
this.obs$ = this.acc$.pipe(switchMap(v => v));
}
addObservable(newObservable: Observable<T>) {
this.acc$.next(merge(this.acc$.getValue(), newObservable))
}
}
const foo = new ColdSubject<number>();
# I know this observable is waiting for withLatestFrom because "Tap yeet" is logged
of('yeet').pipe(
tap(v => console.log(`tap ${v}`)),
withLatestFrom(foo.obs$)
).subscribe(v => {
console.log(`WithLatestFrom ${v}`);
});
# This observable will begin emitting 5 seconds into the script, because I wait 5 seconds to subscribe to it
foo.addObservable(
interval(1000).pipe(
take(5),
tap(v => console.log(`Interval ${v}`))
)
);
# Subscribe 5 seconds into script start, so I know that my ColdSubject only evaluates its observables once they're subscribed to
setTimeout(
() => foo.obs$.subscribe(v => console.log(`Subscribe ${v}`)),
5000
);
Why does foo.obs$ emit several times, while the operation waiting for its latest value not emit?
Looking at the source code one can see that withLatestFrom is triggered by_next which is fired by the source Observable calling next:
protected _next(value: T) {
if (this.toRespond.length === 0) {
/**
* value - emitted by the source Observable
* ...this.values - emitted by the Observables passed to `withLatestFrom`
*/
const args = [value, ...this.values];
if (this.project) {
this._tryProject(args);
} else {
this.destination.next(args);
}
}
}
Your issue is that your source completes right away, while the Observable passed to withLatestFrom has not emitted yet. by the time foo.obs emits, your source Observable has long since completed.
What I would recommend of using in your case is combineLatest as demonstrated below:
combineLatest(of("yeet"), foo.obs$)
.pipe(
tap(v => console.log(`tap ${v}`)),
)
.subscribe(v => {});
of('yeet') emits and is complete so withLatestFrom will complete as the source is complete.
Change your subscription to
of('yeet').pipe(
tap(v => console.log(`tap ${v}`)),
withLatestFrom(foo.obs$)
).subscribe({ complete: () => console.log('yeet complete') });
and you will see it is infact complete.
https://stackblitz.com/edit/rxjs-6-opeartors-2nuam1?file=index.ts

RxJs: finalize for not complete observable

I have the following code:
this.userService.get().pipe(
catchError(() => EMPTY) // Do something here
).subscribe(() => {}) // And here
I want to do some stuff no matter there was an error or not. Just like finalize but for not completed observable. I tried to use tap, but it works only if there's no error. I don't want to duplicate my code and add it to the catchError. What should I do?
there's no other way to do it with catchError without touching it in the current example . Because everything after catchError won't get any notification, it's like an observable that never emits.
you can use 2nd argument of tap, it is triggered on error, the same as in subscribe.
this.userService.get().pipe(
tap(
() => console.log('emit'),
() => console.log('error'),
() => console.log('complete'),
),
// catchError(() => EMPTY), // closing the stream and hiding the error.
// repeat(), // in case if you want to resubscribe once the stream has been closed.
).subscribe(
() => console.log('emit'),
() => console.log('error'),
() => console.log('complete'),
);
Emit a default value in catchError.
this.userService.get().pipe(
catchError(() => of(null)) // catch error and emit null instead (or some other value)
).subscribe(value => {
// do something here,
// value will be null when this.userService.get() errors */
})
You should consider moving your error handling with catchError into the Service.

Resources