Home > OS >  How do I make a component wait for async data from service
How do I make a component wait for async data from service

Time:08-01

A service is fetching api data and I need to keep and use it in many other components every time the url changes (router links) without calling api again. Api should fetch only when the service runs.

I need to make the component method wait until data is available.

Service

  constructor(private http: HttpClient) {
    this.fetchData();
  }

  fetchData(): void {
    this.http.get(this.endpointUrl).subscribe((res)=>{
      this.data = res
    });
  }

  getData(selectedCategory): DataType {
    return this.data.find(cat => cat === selectedCategory)
  }

Component

  ngOnInit(): void {
    this.activatedRoute.paramMap.subscribe(
      (params: ParamMap) => {
        // I need params before calling service method
        // this method must wait until the "data" is available in service.
        this.selectedCategory = this.service.getData(params.category);
      }
    );
  }

CodePudding user response:

I would suggest using the async pipe (if you need to display the data) and you should avoid nesting subscriptions.

For data persistence over page refresh, you'll need to use the local storage or the session storage. I'm not a big fan of this, but I guess it depends on your requirements.

You can manage your state with the help of a BehaviourSubject, but if you want to do more complicated stuff and to keep your code clean, I'll recommend using a state management library like @ngrx/component-store .

Your service

private readonly _state = newBehaviourSubject<Record<string, DataType>>(JSON.parse(localStore.getItem('state')) ?? {});
readonly state$ = this._state.asObservable();


getData(category: string) {
        return of(category).pipe(
            withLatestFrom(this.state$), // you don't want to do something like this.state.pipe(...) because you'll resubscribe to it multiple times because the implementation below
            switchMap(([key, data]) => {
                    if (key in data) return of(data[key]); // return the found category

                    return this.http.get(this.endpointUrl).pipe(
                        tap(res => {
                            const updatedData = { ...data, [key]: res };
                            this._state.next(updatedData); // update your state
                            localStorage.setItem('state', JSON.stringify(updatedData) // update local storage
                        })
                    );
                }
            )
    }

Component

readonly selectedCategory$ = this.activatedRoute.paramMap.pipe(
   map(paramMap => paramMap.get('category')), // I assume that you expect a string
   filter(category => !!category) // it makes sure that the result is not null or undefined
   switchMap(category => this.service.getData(category)),
   catchError(error => {...}), // handle your errors
   filter(res => !!res) // if you want to make sure the response is not null
)

CodePudding user response:

Without many changes, it would be possible to subscribe to fetchData() which instead returns an observable transporting requested data. We use tap for the side effect of also setting this.data in the Service. Don't forget to unsubscribe()! Also mind some syntax errors, I didn't check for them. Suggesting to add typings for <any>.

  fetchData(): Observable<any>{
   return this.http.get(this.endpointUrl).pipe(
      tap(res => this.data = res),
      catchError(console.log(err))
    )
  }
  ngOnInit(): void {
    this.activatedRoute.paramMap.subscribe(
      (params: ParamMap) => {
        this.myService.fetchData().subscribe(res => {
        this.selectedCategory = this.service.getData(params.category);
        // Or use
        // this.selectedCategory = this.res.find(cat => cat === params.category)
         });  
      }
    );
  }
  • Related