1

const observable = new rxjs.BehaviorSubject(0);

observable.subscribe(v => console.log(v));

rxjs
  .of(1)
  .pipe(rxjs.operators.delay(500))
  .subscribe(v => observable.next(v));
  
observable.next(2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.1/rxjs.umd.js"></script>

As you can see, the observable above emits 3 values in order : 0, 2, 1.

Would it be possible to cancel (or ignore) the value "1" when the value "2" is emitted ? (Without closing the subscription)

  • There are better ways to emit numbers one after another, rxjs timer for example. in any case, if you have an observable with subscription and want that new value that emited will cancel the last subscription and switch to a new one read about switchMap. But i'm not sure if it's possible in this code that you added above – Lagistos Apr 25 '19 at 14:26
  • @Lagistos this is a [mcve]. I used numbers to be quick. In reality, the `next`s are called on mouse events. I would like to cancel the delayed `mouseenter` on `mouseleave`, because otherwise, the leave emits, then the enter does too. –  Apr 25 '19 at 14:28
  • Based on what condition you want to ignore the last value? You could use `ignoreElements()` before `delay` if you never want to receive the value from `of`. Or you can also use `EMPTY` as your source Observable. – martin Apr 25 '19 at 14:31
  • There's no condition, I would like to cancel a delayed emition if a new emition comes up. I know that you can use SwitchMap for that, but I don't know how to use it for a single observable. –  Apr 25 '19 at 14:36
  • @trichetriche i fixed my answer now that i know the purpuse of this, you are looking for debounceTime wrote you a new answer below – Lagistos Apr 25 '19 at 14:49
  • 1
    use `debounceTime(500)` instead of `delay(500)`. @trichetriche – n00dl3 Apr 25 '19 at 16:20
  • Your question is unclear, you have 2 subscription to different Observables in your example, and ask your question as if there was only a single observable with a single subscription. @trichetriche – n00dl3 Apr 26 '19 at 09:10
  • @n00dl3 I have a subscription to a single observable. The second one is a subscription to `of`, which can just be replaced by a timeout. I thought I would use `of` to keep the RxJS context. But in the end, there's a single observable I have subscribed to that interests me. –  Apr 26 '19 at 10:39

3 Answers3

6

Seems like you need to switchMap from your source and apply a delay inside of it.

switchMap(value =>
 of(value).pipe(delay(50))
)

An illustration and a playground for switchMap with a delay:

delay with a switchMap

And heres a snippet:

const {Subject, of} = rxjs;
const {switchMap, delay} = rxjs.operators;

const subject = new Subject(0);

subject
  .pipe(
     switchMap(value =>
       // switchMap to a delayed value
       of(value).pipe(delay(500))
     )
  )
  .subscribe(v => console.log(v));

// immediately emit 0
subject.next(0);

// emit 1 in 1 sec
setTimeout(()=>{
  subject.next(1);
}, 1000)

// emit 2 in 1.2 sec
setTimeout(()=>{
  subject.next(2);
}, 1200)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.1/rxjs.umd.js"></script>

Heres an example with mousehover

const {fromEvent, merge, of, EMPTY} = rxjs;
const {switchMap, delay, mapTo} = rxjs.operators;

const button = document.getElementById('pane');
const mouseOver$ = fromEvent(button, 'mouseover').pipe(
  mapTo(true)
);

const mouseOut$ = fromEvent(button, 'mouseout').pipe(
  mapTo(false)
);

merge(mouseOver$, mouseOut$)
  .pipe(
     switchMap(value => {
       if (!value) { return EMPTY; }
       return of('mouse is over').pipe(delay(500))
     })
  )
  .subscribe(v => console.log(v));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.1/rxjs.umd.js"></script>

<style>
#pane{
  margin: 1rem;
  display: inline-block;
  width: 5rem;
  height: 5rem;
  background: rebeccapurple;
}</style>

<div id="pane"><div>

Hope this helps

kos
  • 5,044
  • 1
  • 17
  • 35
  • I tried switchMap but it didn't work. The strange part is, I then tried a sub-subscription (putting a `subscribe` into the callback of another `subscribe`), and it worked ... –  Apr 26 '19 at 10:40
  • @trichetriche, having a sub-subscribe would be a bad practice. Probably theres some minor thing missing in your approach. Could you share this code part? – kos Apr 26 '19 at 10:44
  • totally agreed. That's why I have just abandoned this idea and went on something entirely different that took me like 2 minutes ... And hence, why I can't share the code with you. I'm going to delete my question anyway, but thank you for your input ! EDIT : can't delete the question ... –  Apr 26 '19 at 10:50
  • @trichetriche, I've added a snippet with mouseover. Might be helpful for further implementations. GL :) – kos Apr 26 '19 at 10:54
  • @trichetriche, "can't delete the question" -- it might've been due to me editing the answer. So check out my update and try again ;) – kos Apr 26 '19 at 10:56
  • 1
    Thanks for the extensive answer, even 2.5 years later you helped me :) – Spray'n'Pray Nov 09 '21 at 14:52
4

The operator you are looking for is debounceTime :

debounceTime

Emits a value from the source Observable only after a particular time span has passed without another source emission.

source

rxjs.interval(100)
  .pipe(
    rxjs.operators.take(10),
    rxjs.operators.debounceTime(500)
  )
  .subscribe((v)=>{
    console.log(v);
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.1/rxjs.umd.js"></script>
Community
  • 1
  • 1
n00dl3
  • 21,213
  • 7
  • 66
  • 76
  • As stated in the other answer, it doesn't work for me, because I am using mouse events : using this operator increases the time waiting for the animation to happen. –  Apr 26 '19 at 06:01
1

So for mouse enter and leave you are looking for debounceTime for example:

const observable = new BehaviorSubject(0);
observable
  .pipe(debounceTime(500))
 .subscribe(console.log);

observable.next(1),
observable.next(2);
setTimeout(() => observable.next(3) , 1000)

In this example that will print 2 and after one seconed 3. After each emitited value the observable wait for 500 ms and if there are no new value it will print in the subscribe else it will cancel the last one and start this proccess again, hope this will solve your problem

Lagistos
  • 3,539
  • 1
  • 10
  • 18
  • This doesn't do what I ask, it doesn't cancel the previous values, it just waits 500ms before emitting the next value –  Apr 25 '19 at 15:56
  • It's cancel, you can see that it's not printing 0 and 1. i add stackblitz link. https://stackblitz.com/edit/rxjs-cveugz?devtoolsheight=60 – Lagistos Apr 25 '19 at 16:18
  • Indeed ! Thank you for that, I'm going to try this tommorow ! –  Apr 25 '19 at 17:06
  • I've tried it and it doesn't work : the issue is that the emitions aren't linear like your exemple. Since it's bound to a mouse event, it just react instantly. In your case, the operator adds a delay on every event, making it very not user-friendly. –  Apr 26 '19 at 06:00
  • Can you explaine the exact feature you are trying to create, you said that is something with mouse enter and leave but what about them?, this will help me to give you an idea how to solve this – Lagistos Apr 26 '19 at 06:26
  • I am creating a menu that can be expanded on mouse enter. Every item of that menu can then display a submenu as a popover. If the menu is not fully opened (500ms to open), the submenu should not pop up. Right now, the issue is that if the user enters the menu then leaves it instantly, it emits a false value, then a true value (because of the delay), making the submenu pop after the menu is closed. –  Apr 26 '19 at 06:32