Search code examples
angulartypescriptsubject-observer

How to wait for observables in template (using async pipe) to be subscribed to before running initialisation code?


I have a HTTP service which returns some information when a given item ID is passed to it. This is done through a Subject, which received the first piece of data in then ngOnInit method.

I then use the async pipe to display the data returned by the service in the HTML.

My problem is that the async pipe hasn't subscribed to the observables at the point where I call selections.next with the first item ID - therefore this one isn't displayed on initialisation.

How can I wait until the async pipe has subscribed to the Observable before I send the first piece of data to the subject to begin the first HTTP request?

I've tried the different lifecycle hooks but none seem to have worked.

import { Component, OnInit } from "@angular/core";
import { Observable } from "rxjs/Observable";
import { Subject } from "rxjs/Subject";

import { ExampleService } from "./example.service";

import "rxjs/add/operator/switchMap";

@Component({
  template: `
    <div>
      <div *ngFor="let time of times | async">{{ time }}</div>
    </div>
  `,
})
export class ExampleComponent implements OnInit {

  times: Observable<string[]>;

  constructor(
    private exampleService: ExampleService
  ) { }

  ngOnInit() {

    var itemIds = new Subject<number>();

    this.times = itemIds
      .switchMap(itemId => this.exampleService.getData(itemId))
      .map(data => this.calculateTimes(data));

    // Pass an item ID to the subject.
    // This is done periodically as well as
    // on init.
    itemIds.next(10);
  }

  calculateTimes(data: string[]) {
    /*
     * Some processing code.
    */

    return data;
  }
}

Solution

  • Use a behavior subject instead of a subject.

    the behavior subject saves it's last value and sends it to new subscribers on subscription.

    import { BehaviorSubject } from "rxjs/BehaviorSubject";
    
    
    var itemIds = new BehaviorSubject<number>(null);
    

    The behavior subject needs to be initialized with a value. It exists to solve this timing problem when you don't know if your value or your subscriber will arrive first.

    If you're trying to avoid dup calls, you can set up a local store pattern:

    times: BehaviorSubject<string[]> = new BehaviorSubject<string[]>();
    
    
    var itemIds = new Subject<number>();
    
    itemIds.switchMap(itemId => this.exampleService.getData(itemId))
        .map(data => this.calculateTimes(data)).subscribe(this.times);
    

    this way your only subscriber to the http call is your behavior subject that you subscribe to in template.