Jacob Windsor
Jacob Windsor

Reputation: 6980

Use previous value from Observable in map function

I have a stream of image lists resulting from an event, like this:

const imageSrcs$ = Rx.Observable.fromEvent($('#clicker'), 'click')
  .flatMap(() => this.imageService.getImagesForUser(userUid))
  .scan((acc, cur) => acc.concat(cur), [])
  .startWith([]);

imageService.getImagesForUser(userId) retrieves a limited amount of images from the database and returns a list of images:

[
   {
       id: number,
       timestamp: string,
       ...
    },...
]

So this observable just takes that result and applies the accumulator (scan()) over it. However, getImagesForUser also takes an extra parameter startAt which is the timestamp to start the retrieval at. For my case, this should be the timestamp of the last image in the array returned by the observable.

I could, of course, do something like this:

lastTimestamp;
...
const imageSrcs$ = Rx.Observable.fromEvent($('#clicker'), 'click')
  .flatMap(() => this.imageService.getImagesForUser(userUid, this.lastTimestamp))
  .scan((acc, cur) => acc.concat(cur), [])
  .startWith([])
  .subscribe(list => this.lastTimestamp = list[list.length - 1].timestamp);

But this doesn't really seem like separation of concerns and I'm interested to know if I can just do this in one observable. I would think something like this would work:

const imageSrcs$ = Rx.Observable.fromEvent($('#clicker'), 'click')
  .startWith([])
  .filter(val => Array.isArray(val))
  .last()
  .map(val => val[val.length - 1].timestamp)
  .flatMap(startAt => this.imageService.getImagesForUser(userUid, startAt))
  .scan((acc, cur) => acc.concat(cur), []);

But of course, it doesn't because the click events are filtered out.

Upvotes: 4

Views: 2679

Answers (1)

martin
martin

Reputation: 96959

The easiest way I can think of is like this (this just simulates your use-case but I think you'll get the point):

function getImagesForUser(startAt) {
  return Observable
    .range(startAt * 5, 5)
    .toArray();
}

const imageSrcs$ = Observable.defer(() => {
    let startAt = 0;
    return Observable.timer(0, 1000)
      .flatMap(() => getImagesForUser(startAt))
      .do(arr => startAt = arr[arr.length - 1]);
  })
  .scan((acc, cur) => acc.concat(cur), [])
  .startWith([])
  .subscribe(console.log);

Eventually the scan() operator could go inside the .defer() callbacks as well.

Just instead of clicks I'm using Observable.timer(0, 1000). I don't know if there's any better way to do this without using any state variables outside the Observable chain.

Upvotes: 3

Related Questions