Murhaf Sousli
Murhaf Sousli

Reputation: 13296

Observable iteration complete event

I'm subscribing to a BehaviorSubject routes$ that emits an array of directions

e.g. ['left', 'top', 'left']

Then I want to log each element of this array with 200ms delay, now after all element have been logged, I want to log route finished.

I tried the complete event ()=>{} and finally() operator but they both don't fire since routes$ is still alive and might emits new directions.

/** Stream recent directions */
this.route$.switchMap(() => {
  return Observable
    .interval(200)
    .timeInterval()
    .take(this.route$.getValue().length)
}).subscribe(
  (v) => {
    if (v.value === this.route$.getValue().length) return;
    let i = v.value;
    this.moveStep(this.route$.getValue()[i]);
  }
);

Currently I'm using this workaround

.subscribe(
  (v) => {
    if (v.value === this.route$.getValue().length) return;
    let i = v.value;
    this.moveStep(this.route$.getValue()[i]);

    /** Check if it is the last iteration */
    if(i + 1 === this.route$.getValue().length){
      console.log('route finished');
      this.state = PlayerState.Idle;
    }
  }
);

Is there a native way to achieve this in observable?

Upvotes: 2

Views: 499

Answers (2)

martin
martin

Reputation: 96891

If I understand correctly what you're trying to do you want to emit a value when the array passed to the BehaviorSubject has been completely iterated and you want to get just the last value.

Try to avoid using .getValue() directly from inside the Observable chain because the content of .getValue() is something you should be working with in the chain already.

var routes$ = new BehaviorSubject(['default']);

routes$.switchMap((arr) => {
    return Observable.interval(200)
        .take(arr.length)
        .map(i => arr[i])
        .timeInterval()
        .do(null, null, () => console.log('Complete'));
}).subscribe(value => {
    console.log(value);
});

routes$.next(['left', 'top', 'left']);

setTimeout(() => {
    routes$.next(['top', 'right', 'down']);
}, 1000);

This prints to console:

TimeInterval { value: 'left', interval: 208 }
TimeInterval { value: 'top', interval: 241 }
TimeInterval { value: 'left', interval: 207 }
Complete
TimeInterval { value: 'top', interval: 204 }
TimeInterval { value: 'right', interval: 200 }
TimeInterval { value: 'down', interval: 205 }
Complete

See live demo: https://jsbin.com/muxasa/5/edit?js,console

Notice that the ['default'] is never reemitted because switchMap() receives a new array when calling routes$.next(['left', 'top', 'left']);

Upvotes: 3

Mark van Straten
Mark van Straten

Reputation: 9425

If your Observable is not completed yet you can use debounce() to wait for a certain amount of time before doing something. Other options would be to buffer your values by time/amount and then after emitting the buffer do something.

Upvotes: 1

Related Questions