0

I have a simple HTTP call:

  getContributors(pageNumber): Observable<any> {
    const url = `${this.apiBaseUrl}/orgs/angular/public_members?&page=${pageNumber}`;
    const requestOptions = this.getRequestOptions();

    return this.http.get(url, requestOptions);
  }

the call is made within a ngrx effect.

...
return this.contributorsService.getContributors(payload.pageNumber)
        .delay(new Date(Date.now() + Math.random() * 500))
        ... // mergeMap etc. here

However, while the HTTP call returns an array of multiple values, they're in any case emitted all at once.

Am I misunderstanding the purpose of delay() operator (and in this case, how do I achieve the expected result?) or am I using it the wrong way?

Note: I imported the augmentation import 'rxjs/add/operator/delay';

UPDATE: to clarify, the expected result is: I want the array to be split and each single value within the array to be emitted separately at a given (constant) time

UPDATE 2: so, the array is actually split by

 .flatMap(data => Observable.from(data))

In fact, If

 .do(value => console.log(value))

each value is printed individually.

However, if instead of .do(....)

I put

.delay(3000)
.do(() => console.log(new Date())

I can see the delay is not respected at all (except for the fact it actually awaits 3000 to emit all the values in a sequence with no delay - in a row, i.e. multiple console.logs)

dragonmnl
  • 14,578
  • 33
  • 84
  • 129
  • 2
    what exactly is your expected result? delay takes your value and... delays it for however long, if your value is an array, then it doesn't split the array into a stream of values or anything like that, it delays the entire single value. – bryan60 Dec 01 '17 at 18:09
  • @bryan60 exactly, I want the array to be split and each single value within the array to be emitted separately at a given (constant) time – dragonmnl Dec 01 '17 at 18:10
  • please update your question with the expected result, writing up an answer – bryan60 Dec 01 '17 at 18:11
  • @bryan60 done. thanks! – dragonmnl Dec 01 '17 at 18:11

2 Answers2

3

ok, so the thing about arrays in observable streams, is they're just like any other single value in a stream. Your http call returns an array value, so it returns the entire array as a single value which goes all at once. If you want to split an array into multiple values, you can certainly do that, but you need to tell rxjs to do so, my preferred method is to use flatMap from like this:

.flatMap(arrVal => Observable.from(arrVal))

This operation will flatten your array value into an observable stream of values, so you could use it like this:

return this.contributorsService.getContributors(payload.pageNumber)
    .flatMap(data => Observable.from(data))
    .delay(Math.random() * 500) // you should just feed a ms value to delay if you want constant time interval, date parameters mean delay till that date, so they'll all just flow through at that date

However, the above won't work because delay shifts the entire stream by the time, rather than each item, so to accomplish a flattening and separating, we need to get a little more explicit, drawing from this answer: Separate observable values by specific amount of time in RxJS

return this.contributorsService.getContributors(payload.pageNumber)
           .switchMap(data => Observable.interval(Math.random() * 500)
                                        .take(data.length)
                                        .map(i => data[i]))

This way we manually flatten the array at an evenly spaced interval.

This version will also work and may feel slightly cleaner:

return this.contributorsService.getContributors(payload.pageNumber)
           .flatMap(data => Observable.from(data))
           .zip(Observable.interval(Math.random() * 500), (d,i) => d)

This just uses the zip operator to zip each item with an observable that emits on an interval.

Now you should see your desired behavior of each array item coming through one at a time at a constant interval. But, if you're using the http service, you probably need to call .json() on your response to get the value as an array, like this:

return this.http.get(url, requestOptions).map(res => res.json());
bryan60
  • 28,215
  • 4
  • 48
  • 65
  • thanks. seems to work. let me look into it and then I'll accept the answer – dragonmnl Dec 01 '17 at 18:26
  • actually, it doesn't seem to work. I'll update my question with details @bryan60 – dragonmnl Dec 01 '17 at 19:56
  • it's because you're using a Date as an input to delay, check my comment on why you shouldn't do that and should jsut use ms, the .now() is only evaluated once, not for every item, you need a constant time, there is no reason at all for you to use a date input. – bryan60 Dec 01 '17 at 20:03
  • sorry, I missed that. Nonetheless even if for example I pass 3000 (see updated question), the behavior doesn't change (except for the fact it actually awaits 3000 to emit ALL THE values and SEPARATELY) – dragonmnl Dec 01 '17 at 20:11
  • yea i just read the docs on delay operator, it seems to shift the entire stream rather than each item. I will change my answer. – bryan60 Dec 01 '17 at 20:17
  • Thanks. a bit late now, will try it tomorrow ;) Thanks for the effort! – dragonmnl Dec 01 '17 at 20:31
  • While both solutions (including Richard's one) are interesting, I'll accept it as I like the .zip approach (more intuitive). Worth noticing it didn't work without first updating rxjs to the latest version – dragonmnl Dec 02 '17 at 13:36
2

You can use delay() by flatMap'ing a second time,

return this.contributorsService.getContributors(payload.pageNumber)
  .flatMap(x => x)                                            // to single values
  .flatMap(x => Observable.of(x).delay(Math.random() * 500))  // apply delay

const Observable = Rx.Observable

Observable.of([1,2,3])
  .flatMap(x => x)
  .flatMap(x => Observable.of(x).delay(Math.random() * 500))
  .subscribe(console.log)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.3/Rx.js"></script>
Richard Matsen
  • 20,671
  • 3
  • 43
  • 77