skip to Main Content

I am trying to execute a method 1000 milliseconds after all items in pipe finish, and I don’t want the result, nor do I want it to change the current value of the pipe. What I have here is what I am doing which works, but it isn’t using rxjs completely with the setTimeout.

selectionChanged$ = this.selectionChanged.pipe(
 map(() => this.someValue),
 tap(() => /* do stuff */),
 tap(() => setTimeout(() => this.doSomething(), 1000))
);

I then tried this, but it does two things that I don’t want:

  1. It waits for the timer to complete
  2. It changes the final result to a number
doSomething$ = timer(1000).pipe(
  tap(() => this.doSomething()),
);

selectionChanged$ = this.selectionChanged.pipe(
 map(() => this.someValue),
 tap(() => /* do stuff */),
 switchMap(() => this.doSomething$)
);

Is there a way to get the desired result without (aka "Set it and forget it"):

  1. Using subscribe()
  2. Waiting for the timer to complete
  3. Modifying the final result

2

Answers


  1. Do you mean like this? I use delay of 1000s and then finally using map, set the last emit value to zero.

    import './style.css';
    
    import { rx, of, map, fromEvent, tap, timer, delay } from 'rxjs';
    const someValue = 'qwerty';
    
    const doSomething = () => {
      console.log('doSomething');
      return 0;
    };
    
    fromEvent(window, 'click')
      .pipe(
        map(() => someValue),
        tap<unknown>(() => {
          console.log(Math.random());
        }),
        delay(1000),
        map(() => doSomething())
      )
      .subscribe(console.log);
    

    Stackblitz Demo

    Login or Signup to reply.
  2. You could do something like this:

    const observable$ = source$.pipe(
      mergeMap(val => timer(delay).pipe(
        tap(() => doSomething()),
        ignoreElements(),
        startWith(val)
      ))
    );
    

    The idea here is to create an observable that immediately emits the value (startWith), then after the duration (timer) executes the side effect function. We use ignoreElements to block emission from the timer.

    The benefit here over using setTimeout is that if the consumer unsubscribes, the side effect logic will not be executed.

    You could even roll it into a custom operator:

    function delayTap<T>(fn: (...args: T[]) => void, delay: number): OperatorFunction<T, T> {
      return mergeMap(val => timer(delay).pipe(
        tap(() => fn(val)),
        ignoreElements(),
        startWith(val)
      ))
    }
    
    const observable$ = source$.pipe(
      delayTap(v => doSomething(v), 1000)
    );
    

    Here’s a StackBlitz.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search