Issue with data provider

Does anybody know, what I am doing wrong? I try to get a local json - assign it to a class variable and return the response as an observable — but I am not able to make this work.

export class CategoriesService {
  private categories: CategoryItem[] = null;

  constructor(
    private http: HttpClient
  ) { }

  getCategories(): Observable <CategoryItem[]> {
    this.http.get<CategoryItem[]>('/assets/data/categories.json').subscribe(
      response => {
        console.log(response);
        this.categories = response;
      },
      error => {},
      () => {
          return of(this.categories);
      });
  }

why don’t you simply do something like:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({

providedIn: 'root'

})

export class YourService {
 yourItem: any;

 constructor(
   private httpClient: HttpClient,
 ) {
     this.jsonReader('./assets/yourJson.json');
 }

 async jsonReader(jsonFile): Promise<any> {
   this.httpClient.get(jsonFile).subscribe((data) => {
     this.yourItem = data;
     console.log(this.yourItem);
   });

 }


}


and then in the html file:

<div *ngFor="let item of youItem; let i = index">
  <p>{{yourItem.element}} nr. {{i}}</p>
</div>


1 Like

Hi
both examples do not show the @Injectable decorator attached to the class, so the HttpClient injection won’t work in the constructor.
Not sure if this helps, but that is at least something I see

true that: but in my example, i just wrote a “generic” code, to be used within a service provider or just a simple page component, anyway i fixed it

1 Like

I think you should use from() rxjs operator
for more information, you can visit this link from - Learn RxJS

I wasn’t paying attention - above example function is returning nothing, not even because there is a return statement in the subscription. And if you would put ‘return’ in front of the http.get then it would not return an Observable, but a Subscription - which by definition won’t hold the data on the stream.

In fact, the subscription is best to be done in the calling function, so outside the service - also to handle the relevant UI in case of errors. RxJS can help transforming the data - if needed.

Big compliments though for the usage of types (Observable<Category>)!

This is what the provider ideally does:

getCategories(): Observable <CategoryItem[]> {
    return this.http.get<CategoryItem[]>('/assets/data/categories.json')
  }

And then the calling function does the subscription handling - via async pipe - so you don’t need to explicitly handle unsubscriptions (not perse an issue with http Observables).

And if you want to monitor the stream just for debugging:

getCategories(): Observable <CategoryItem[]> {
    return this.http.get<CategoryItem[]>('/assets/data/categories.json')
             .pipe(tap(res=>{console.log('HTTP res',res);}));
  }

Whether you to store the last known value of the Categories in the service (as per code example of @ciccilleju ) could be a matter of discussion. Do you want to avoid many http calls? If so, then still one should wonder if you would like to do so using an internal variable - as you likely need an Observable getter anyway (using BehaviorSubject) - or do the getting in your lifecycle hooks - but then you need to ensure immutability of the variable, to avoid inconsistent state in your app.

In case you insist on having the service holding the last value in an internal variable, then you could but that assignment in the tap operator.

excellent explanation @Tommertom

1 Like

It works like this now - and yes I want as less calls if possible and cache it in the application… But I prob should change the Observable to an Subject, right? For sure – this JSON file will be replaced with a real API call to a graphQL.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { find } from 'rxjs/operators';
import { ICategoryItem } from '../../types';

@Injectable({
  providedIn: 'root'
})
export class CategoriesService {
  private categories: ICategoryItem[] = null;
  
  constructor(
    private http: HttpClient
  ) {
    this.http.get<ICategoryItem[]>('/assets/data/categories.json').subscribe(result => {
      this.categories = result;
    });
  }

  getCategories(): ICategoryItem[] {
    return this.categories;
  }

  getCategory(id: number):ICategoryItem {
    return this.categories.find((item) => item.id === id )
  }
}

Where by ICategoryItem is defined as:

export interface ICategoryItem {
    id: number;
    name: string;
    description: string;
    active_flg: number;
    color?: string;
}

Indeed better, and yes I would create a BehaviorSubject and next the value from the http into it

BehaviorSubject is better than Subject as it provides the last value once someone subscribes to its Observable

The getter function should return the BehaviorSubject as Observable via the asObservable() method or even through typecasting which saves a bit on runtime

The pattern you have now will allow consumers tho change the data within the provide without it knowing

I wouldn’t say BehaviorSubject is always better than an ordinary Subject, only when you have a reasonable default. In a situation where you have to special-case the type to deal with “don’t know yet”, I’d prefer ordinary Subject.

1 Like