0
votes

I am getting data from an API server which I use on multiple components. However, if I try to share the data using a service, the components process before the data is retrieved.

I can retrieve the data in each of the components, but then the user must wait for the data to be retrieve before loading each page (component). The data doesn't change, so I would like to be able to http.get() the data once and manipulate the views in the various components.

I can also pass the data from parent to child - but, since their are multiple pages, sharing the data via the router and @Input() doesn't work.

NOW, I am trying to use a shared service. The service eventually returns the data, but the pages attempt to load before the data is retrieved which means no data is displayed.

app.component.html

<div class="router-body">
      <router-outlet ></router-outlet>
    </div>

api.service

createData(url): Observable<any> {
    this.http.get<Person[]>(url)
        .subscribe(people => {
          console.log("in createData()");
          this.people = people;

        });
        return of(this.people);
  }

people.component

  ngOnInit() {
    this.apiService.createData(this.apiService.personURL)
    .subscribe( people => { 
        console.log("data within people.component");
        console.log(this.people);
        this.people = people });
  }

people.component is getting 'undefined' for this.people api.service returns the data eventually, but the people page never displays it.

I need a way to 1 - pull the data ONCE 2 - display the data on the people page (or other pages)

2

2 Answers

1
votes

I suggest to use a subject for that kind of sharing data:

first create a Subject of type Person[] in your dataservice:

ppl$: Subject<Person[]> = new Subject();

Then define a getter:

getPplObs(): Observable<Person[]> {
  return this.ppl$.asObservable();
}

when u set your data on the service, pass the value to the subject:

createData(url): Observable<any> {
    this.http.get<Person[]>(url)
        .subscribe(people => {
          console.log("in createData()");
          this.people = people;
          this.ppl$.next(people);
        });
  }

Now u only need to subscribe to that Observable in your components ngOnInit:

ngOnInit() {
  this.apiService.getPplObs().pipe(takeUntil(this.unsubscribe$)).subscribe(ppl => this.people = ppls);
}

For properly unsubscribing on destroy of component I use takeUntil. Create a Subject

unsubscribe$: Subject<boolean> = new Subject();

then in ngOnDestroy lifecycle:

ngOnDestroy() {
  this.unsubscribe$.next(true);
  this.unsubscribe$.complete();
}

regards

0
votes

With the following you are always returning people first, since this is asynchronous.

createData(url): Observable<any> {
  this.http.get<Person[]>(url)
    .subscribe(people => {
        // takes x time to complete
        console.log("in createData()");
        this.people = people;
     });
     // this line is executed while waiting for above response
     return of(this.people);
}

I would instead always return an observable of either the http-request or then an observable of people. You can use tap to first store the data in the variable:

import { tap } from 'rxjs/operators';

// ...

people = <Person[]>[];

createData(url): Observable<Person[]> {
  if (this.people.length) {
    return of(this.people)
  }
  return this.http.get<Person[]>(url)
    .pipe(
      tap(people => this.people = people)
    )
}

And remember to unsubscribe in your component.