Fetch data once with Observables in Angular 2

If you want multiple children to subscribe to the same observable, but only execute the observable once you can do the following.

Note that this does adhere to the design of observables since we are executing the observable in the service layer (Observable.fromPromis(stream.toPromise()) when execution should be done from the component subscribing. View https://www.bennadel.com/blog/3184-creating-leaky-abstractions-with-rxjs-in-angular-2-1-1.htm for more.

  //declare observable to listen to
  private dataObservable: Observable<any>;

  getData(slug: string): Observable<any> {

    //If observable does not exist/is not running create a new one
    if (!this.dataObservable) {

        let stream = this.http.get(slug + "/api/Endpoint")
            .map(this.extractData)
            .finally(() => {
                //Clear the observable now that it has been listened to
                this.staffDataObservable = null;
            });

        //Executes the http request immediately
        this.dataObservable = Observable.fromPromise(stream.toPromise());

    }        

    return this.staffDataObservable;
 }

1) You can simply save downloaded data in your service:

export class CustomersService {
  protected _customers: Array<Customer>;

  constructor(public http: Http) {}

  public getCustomers(): Observable<Array<Customer>> {
    return new Observable(observer => {
      if (this._customers) {
        observer.next(this._customers);
        return observer.complete();
      }
      this.http
        .get(this.baseURI + this.url)
        .map((r: Response) => (r.json() as Array<Customer>))
        .subscribe((customers: Array<Customer>) => {
          this._customers = customers;
          observer.next(this.customers);
          observer.complete();
        });
    });
  }
}

2) Shorter approach taking refresh parameter:

export class CustomersService {
  protected _customers: Array<Customer>;

  constructor(public http: Http) {}

  public getCustomers(refresh?: boolean): Observable<Array<Customer>> {
    if (!refresh && this._customers) {
      return Observable.of(this._customers);
    }
    return this.http
            .get(this.baseURI + this.url)
            .map((c: Response) => (c.json() as Array<Customer>))
            .do((customers: Array<Customer>) => {
                this._customers = customers;
            });
    });
  }
}

3) Taking advantage of ReplaySubject:

export class CustomersService {
  protected _customers$: ReplaySubject<Array<Customer>> = new ReplaySubject(1);
  protected _customersInitialized: boolean;

  constructor(public http: Http) {}

  public getCustomers(refresh?: boolean): Observable<Array<Customer>> {
    if (refresh || !this._customersInitialized) {
      this._customersInitialized = true;
      this.http
        .get(this.baseURI + this.url)
        .map((c: Response) => (c.json() as Array<Customer>))
        .subscribe((customers: Array<Customer>) => {
          this._customers$.next(customers);
        });
    }
    return this._customers$.asObservable().skip(+refresh).distinctUntilChanged();
  }
}

And then:

this.customersService.getCustomers()
    .subscribe(customers => this.customers = customers);

You can also expose the always up-to-date customers field from SomeService for read only purposes (like displaying in the templates) this way:

public get customers(): ReadonlyArray<Customer> {
  return this._customers;
}

the share operator give the possibility to use the same stream's result with multiple observers. It could be good but you generate a new observable stream each time you call getCustomers(), there is no point to call share() since you didn't subscribe multiple times to this stream.

If you wanna share the data with multiple observers but make only one http call you simply have to create a second stream, feed by the http one, containing the data. After that, all your components could subscribe to it.

The code could be something like that

@Injectable()
class FooBar {

    public dataStream:Subject<any> = new Subject();

    constructor(private http:Http) {}

    public getCustomers() {
        return this.http
        .get(this.baseURI + this.url)
        .map((response:Response) => response.json())
        .map((data) => {
            this.dataStream.next(data); 
            return data;
        })
    }

}


@Component({})
class BarFooHttpCaller {
    constructor(private foobar:Foobar) {}

    ngOnInit() {
        this.foobar.getCustomers().subscribe(() => { console.log('http done') });
        this.foobar.dataStream.subscribe((data) => {
            console.log('new data', data);
        })
    }
}

@Component({})
class OtherBarFoo {
    constructor(private foobar:Foobar) {}

    ngOnInit() {
        this.foobar.dataStream.subscribe((data) => {
            console.log('new data', data);
        })
    }
}

I would create a parent container, fetch the data once, and pass it to child components using @Input.

Parent:

@Component({
    selector: 'BarFooHttpCaller',
    template: ´<child *ngIf="data.length > 0" [data]></child>´
})

class BarFooHttpCaller {
    private data: any;
    constructor(private foobar:Foobar) {
        this.data = {};
    }

    ngOnInit() {
        this.foobar.getCustomers().subscribe(() => {       
            console.log('httpdone') 
        });
        this.foobar.dataStream.subscribe((data) => {
            console.log('new data', data);
            this.data = data;
        })
    }
}

Child:

import { Component, Input } from '@angular/core';

@Component({
    selector: 'child',
    template: ´<div>{{data}}</div>´
})

export class Child {
    @Input() data: any;

}