9

I wanna stop a observable subscription based on two conditions:

  • Time (using import { timer } from 'rxjs/internal/observable/timer';)

OR

  • Execution status (using the returned object from request that you'll see below)

What is happenning:

It's only stoping execution based on Time (using import { timer } from 'rxjs/internal/observable/timer';)

This is my current code:

The names of attributes, variables and their values ​​have been changed for example purposes:

import { finalize } from 'rxjs/internal/operators/finalize';
import { interval } from 'rxjs/internal/observable/interval';
import { timer } from 'rxjs/internal/observable/timer';
import { takeUntil, first } from 'rxjs/operators';
import { merge, EMPTY, of } from 'rxjs';

.
. // Attributes and Class declaration here
.


async startProcess(): Promise<void> {

    this.isProcessLoading = true;

    const { someId } = await this.exampleService.execute().toPromise();

    const interval$ = interval(1000);
    const timeLimiter$ = timer(10000);

    const request$ = this.exampleService.doRequest();
    
    const isEventFinished$ = EMPTY;

    // My goal here is for the result of this function to return an observable that notifies 
    // if the first parameter emits an event OR if the second parameter emits another. That is, I want to notify if any condition is valid
    const stopConditions$ = merge(isEventFinished$, timeLimiter$);

    const handleSuccess = (object: MyType) => {

      if (object.status === 'FINALIZED') {

        this.object = object;
        isEventFinished$.subscribe();
      }
    };

    const handleError = () =>  this.showErrorComponent = true;

    interval$
    .pipe(takeUntil(stopConditions$))
    .pipe(finalize(() => this.isSimulationLoading = false))
    .subscribe(() => request$.subscribe(handleSuccess, handleError));
}

The code "works" because the timeLimiter$ fires takeUntil after 10s. However, I want the possibility to stop before the time limit...

I want takeUntil to be able to run from here too:

isEventFinished$.subscribe()

if the above snippet performed correctly, it should be stop the interval$, but it does not. That is my problem

What i already tried:

  1. I dont know if two pipes made any difference than use only one like this: .pipe(takeUntil(stopConditions$), finalize(() => this.isSimulationLoading = false)). However, i already tried it and did not work

  2. Already tried to replace this isEventFinished$ for: const isEventFinished$ = of(1) and his subscription by: timeLimiter$.pipe(first()).subscribe(). But this does not works either. In reality, this prevents the request from being executed (I don't know why)

Joao Albuquerque
  • 366
  • 1
  • 3
  • 15
  • You may run into problems importing from `rxjs/internal/operators/` rather than simply `rxjs/operators`. I know my IDE has bitten me a couple times auto-importing from `/internal`. – BizzyBob Jul 16 '20 at 23:51
  • Still not working. There's something with my `merge` operator or the `isEventFinished$` – Joao Albuquerque Jul 17 '20 at 00:21

1 Answers1

9

I just tried this code with a Stackblitz and it "worked" ... but I'm not sure what, exactly, you are trying to do? I then made a few updates to better see what was going on.

See the Stackblitz here: https://stackblitz.com/edit/angular-takeuntil-deborahk

The key change:

const stopConditions$ = merge(this.isEventFinished$, timeLimiter$).pipe(
  tap(s => console.log("stop", s))
);

interval$.pipe(takeUntil(stopConditions$)).subscribe({
  next: handleSuccess,
  error: handleError,
  complete: () => {
    this.isSimulationLoading = false;
    console.log("isSimulationLoading", this.isSimulationLoading)
  }
});

Does that help?

EDIT: I added a "Finished" button to emulate whatever action would cause the finish operation.

Define isEventFinished as an Observable by declaring it as a Subject or BehaviorSubject (BehaviorSubject has a default value, Subject does not).

  isEventFinished$ = new Subject<boolean>();

Then, whenever the finished event occurs, use the next method to emit a value into the isEventFinished stream.

this.isEventFinished$.next(true);

Then this code should work:

const stopConditions$ = merge(this.isEventFinished$, timeLimiter$).pipe(
  tap(s => console.log("stop", s))
);

interval$.pipe(takeUntil(stopConditions$)).subscribe({
  next: handleSuccess,
  error: handleError,
  complete: () => {
    this.isSimulationLoading = false;
    console.log("isSimulationLoading", this.isSimulationLoading);
  }
});

See the updated blitz.

Does that work?

DeborahK
  • 57,520
  • 12
  • 104
  • 129
  • The code "works" because the time limit reaches its limit and runs `takeUntil`. However, I want the possibility to stop before the time limit ... I want `takeUntil` to be able to run from here too: `isEventFinished $ .subscribe ()`. Try changing `if (object.status === "FINALIZED")` to `if (true)` to always execute ` isEventFinished $ .subscribe ()`. It should be stop the interval$, but it does not. That is my problem – Joao Albuquerque Jul 16 '20 at 23:49
  • i edited the question to explain some more. But, in summary, I want `isEventFinished $` to run correctly too – Joao Albuquerque Jul 16 '20 at 23:58
  • 1
    As you have defined it, isEventFinished$ doesn't seem to be an Observable you can emit to. I'll update my stackblitz ... give me a sec. – DeborahK Jul 17 '20 at 00:03
  • i saw it here: [Empty observable](https://rxjs-dev.firebaseapp.com/api/index/const/EMPTY). I don't emit something specific. I wanna just fire a event to stop the execution – Joao Albuquerque Jul 17 '20 at 00:13
  • Thank you, so much!!! I just used the Subject like this and worked too: `new Subject();`. I don't need to pass any data. – Joao Albuquerque Jul 17 '20 at 00:46
  • Although I am very happy with your answer, I am still curious about why the previous implementation did not work. Why the observable with `of ()` or `EMPTY` didn't work, you know? – Joao Albuquerque Jul 17 '20 at 00:49
  • EMPTY doesn't work with the merge because, according to the merge docs: `The output Observable only completes once all input Observables have completed.` The key word is "all" in that sentence. So just completing the Observable assigned to EMPTY won't complete the merged result. Same thing for `of`. Completing the one stream won't complete the merged result. – DeborahK Jul 17 '20 at 01:24
  • I see, but `takeUntil(stopConditions$)` fires for any value that is passed/emitted to it by `stopConditions$`. Not for `stopConditions$` complete event – Joao Albuquerque Jul 17 '20 at 02:40