0

I thought I was smart when I tried to load the first page of data and display it while loading the whole set in the background. I used the following code.

ngOnInit() {
  this.service.getStuff(0, 10)
    .subscribe(suc => this.subset = suc);

  this.service.getStuff()
    .subscribe(suc => this.data = suc);
}

Then, I set the breakpoint in my API fetching and releasing the first call and holding up unreleased the second. However, according to the network tab in my browser, both calls are pending until both are completed.

Am I anywhere close to have the pre-load working or is it far, far off?

The actual call is performed the usual HttpClient and a GET, returning an observable.

Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
  • What you’re describing is not the expected behavior. Something else is happening. – bryan60 Feb 08 '20 at 14:12
  • @bryan60 I gather that, based on the answers and comment. I noticed that invoking the second call as a result of *timer(1000).subscribe(...)* produced the expected behavior, so somehow, the computer clogs those two calls together. You're confirming my original expectations (which I abandoned because I eventually got uncertain and lost confidence in my skills). I'd love to set up a reproducible example but I'm not sure how to, given that it's an issue that combines .NET and Angular... – Konrad Viltersten Feb 08 '20 at 14:18
  • I’ve worked on angular with .NET back ends for years and never come across an issue like this. It’s probably something with how you’re setting the beak point in the API, or if you have some kind of interceptor that could be unruly, or something going on in your service holding up the http client. – bryan60 Feb 08 '20 at 14:24

2 Answers2

1

You'd be better off using some RxJS operator for this.

This will fire both GETs. First come first served.

merge(this.service.getStuff(0, 10), this.service.getStuff()).subscribe(data => {
  // do stuff with data
});

Below, switchMap will make allStuff$ only fire after initialStuff$ has emitted. This will fire the second GET only after the first one emits.

const intialStuff$ = this.service.getStuff(0, 10).pipe(
  share()
);

const allStuff$ = intialStuff$.pipe(
  switchMap(() => this.service.getStuff())
);

intialStuff$.subscribe(...);
allStuff$.subscribe(...)

Note that since none of requests would block rendering, you should definitely go with the first method. It will fetch all the data faster.

blid
  • 971
  • 13
  • 22
  • I'll give it one more try. As far I can see, the second, larger call (that I'm halting on the backend by simply placing a breakpoint there and letting it hang) is listed as pending in the network tab (as it should). The unexpected thing is that the first call, that's been released from the breakpoint and should have returned, also is listed as pending. – Konrad Viltersten Feb 07 '20 at 22:34
  • This is super weird. I still get the same unexpected behavior **but** if I execute the second call after *timer(4000).subscribe(_ => this.service.getStuff()...)* then I get the expected outcome. It's like if Angular bundles the two calls unless one of them is set on timer... I don't quite follow, to be honest. – Konrad Viltersten Feb 07 '20 at 22:47
  • RxJS will bundle them unless you use a flattening operator such as switchMap/concatMap/mergeMap. Those operators say that after one source observable emits, then subscribe to another. It is the act of subscribing that actually fires the HTTP request. This quality of observables, where subscription creates a new data producer, is known as "hot observables" and is true for all HTTP requests. – blid Feb 07 '20 at 23:08
  • Regarding your statement "*it is the act of subscribing that actually fires the HTTP request.*", I agree totally. What surprises me, though, is that based on it, my sample from the question should fire twice independently from each other. However, so is not the case. Or, rather, the status of the call is marked as *pending* and the information is not rendered for the first call, until the second call completes. I suspect it has to do with me running everything on localhost in dev mode, somehow. It would be good to set it up as a separate project but I don'tknow how (.NET back/Angular front). – Konrad Viltersten Feb 08 '20 at 14:11
  • For this particular purpose, I used the first sample based on *merge*. Works as expected. However, I tried to apply the second sample based on *switchMap* and *share*, in a different situation (fetching the ID from the route and then loading data with that as a parameter). Regrettably, it didn't work our the way I hoped so I wonder if you have time to [take a peek at it](https://stackoverflow.com/questions/60131196/second-subscription-doesnt-start-upon-switchmap-from-the-first-one-in-rxjs) to see how I missed your point. – Konrad Viltersten Feb 08 '20 at 21:11
1

Angular HttpClients get() should be returning a new Observable each time and wouldn't exhibit the behavior you describe.

This is entirely dependent on the implementation of this.service.getStuff(). If the implementation looks something like below, it should be returning a new Observable with each call and make it independent of any other call/subscribe.

doStuff() {
    return this.http.get('someUrl');
}

Here's an example of the two observable calls working independent of each other - I put a delay to help demonstrate. When you run this, the first call will complete and will render before the second call.

Component with initialization logic:

ngOnInit(){
this.myService.doSomething('todos/1', 100)
  .subscribe(resp => {
    this.first = resp;
  });

this.myService.doSomething('comments', 1500)
  .subscribe(resp => {
    this.second = resp;
  })
}

Example service:

@Injectable()
export class MyService {
  constructor(private http: HttpClient){}

  doSomething(route: string, withDelay?: number) {
    return this.http.get('https://jsonplaceholder.typicode.com/' + route)
      .pipe(delay(withDelay));
 }
}
spots
  • 2,483
  • 5
  • 23
  • 38
  • Yeah, that's what I expected to happen. I haven't tested it with a delay in the method of the service, only with a breakpoint in the controller I'm calling. But that shouldn't make any difference - once the first invocation is dispatched from the WebAPI, it should be rendered on the client's screen. Not sure how to set up a StackBlitz for that (invcluding an actual backend to call)... – Konrad Viltersten Feb 07 '20 at 22:25
  • I see that I wasn't clear. When I ran your Blitzy I can see in the network tab that both responses arrive at the same moment (seemingly, at least). Then, it takes a certain delay before the second is rendered on the screen. However, my iaim is not delaying the rendition. It's invoking the rendition for the first call while the second is ongoing. Do you know of a way to make the backend that you're invoking in your example to be super slow for certain calls? – Konrad Viltersten Feb 07 '20 at 22:30