Subscribe to same service call without calling service multiple times in Angular 7

Viewed 1374

I have two seperate components which at a given time is visible in the same view. In their ngOnInit()-method I subscribe for the same Observable from a service. This results in two network calls which is unnecessary. How can i share the response from the service to both subscribers so only one network call happens?

My service call code is:

getDashboardViewModel (): Observable<DashboardViewModel> {
return this.get<DashboardViewModel>(Constants.DashboardApiUrl);

And the way i have subscribed it on both components is:

ngOnInit() {
   this.dashboardService.getDashboardViewModel().subscribe(dashboardVM => this.dashboardViewModel = dashboardVM);
}

How can i do so that network call occur only once and both the components get the data. I am using Angular 7.

4 Answers

You could do something like this:

@Injectable()
export class Service{
  dashboardModel: ReplaySubject<Model> = new ReplaySubject<Model>'
  constructor(private _http: HttpClient) {
    this._http.get<Model>(url).subscribe((model: Model) => {
      this._dashboardModel.next(model);
    });
  }
}

and then on your init call in your components simply do:

this._service.dashboardModel.first().subscribe((model) => {});

ReplaySubject allows to retrieve the n last value emitted by the observable.

If the data request can be triggered via a resolver things would be simpler however you could follow one of the previous answers of using a smart container component that wraps your components that need to consume the data. That smart container can trigger the http request but on the service set a pipe with a tap to set the response value to a BehaviorSubject and then on your components that need to data use an async pipe to manage the data subscriptions.

Here is a code example of the service:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';

@Injectable({
    providedIn: 'root',
}) export class ExampleService {

  private _data: BehaviorSubject<any> = new BehaviorSubject({ properties: [], geometry: [] });
  public data$: Observable<any> = this._data.asObservable();

  constructor(private http: HttpClient){}

  set data(data) {
    this._data.next(data);
  }

  get data() {
    return { ...this._data.value };
  }

  public getEarthquakeData(url): Observable<any[]> {
    return this.http.get<any[]>(url)
      .pipe(
        tap(
          (data: any) => this.data = data
        )
      )
  }
}

I have manged to resolve it using MessagingService and EventEmitter.

Messaging Service:

public dashboardViewModel: DashboardViewModel;

constructor(private dashboardService: DashboardService) {}

@Output() emitDashboardViewModelData: EventEmitter<DashboardViewModel> = new EventEmitter();

getDashboardViewModelData() {
    this.dashboardService.getDashboardViewModel().subscribe(dashboardVM => {
      this.dashboardViewModel = dashboardVM;
      this.emitDashboardViewModelData.emit(this.dashboardViewModel);
    });
}

Code of one component:

ngOnInit() {
    this.messagingService.emitDashboardViewModelData.subscribe(dashboardData => {
    this.dashboardViewModel = dashboardData;
    });
}

Code of second component:

ngOnInit() {
    this.messagingService.getDashboardViewModelData();
    this.messagingService.emitDashboardViewModelData.subscribe(dashboardData => {
        this.dashboardViewModel = dashboardData
    });
 }

This code only makes one service call and both the components get data through the emitter when the Messaging Service emits the data

NgRx is a framework for building reactive applications in Angular. NgRx provides state management, isolation of side effects, entity collection management, router bindings, code generation, and developer tools that enhance developers experience when building many different types of applications.

In particular, you might use NgRx when you build an application with a lot of user interactions and multiple data sources, when managing state in services are no longer sufficient.

Click https://ngrx.io/docs

A good substance that might answer the question "Do I need NgRx", is the SHARI principle:

  • Shared: state that is accessed by many components and services.

  • Hydrated: state that is persisted and rehydrated from external storage.

  • Available: state that needs to be available when re-entering routes.

  • Retrieved: state that must be retrieved with a side-effect.

  • Impacted: state that is impacted by actions from other sources.

Related