Search code examples
angulartypescriptobservableangular-servicesangular-httpclient

Http subscribe not working on Service Angular


i'm working on a project and i need to retrieve data from a custom server. I made an httpclient service for handle requests and i encountered this problem: when i try to make the subscription for pass data to the component, nothing happens, but if i do the subscription in the component, evetything works fine. I reassume the code below:

The component:

import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { BehaviorSubject, catchError, map, Observable, of} from 'rxjs';
import { Food } from '../api/api-models';
import { ApiService } from '../api/api.service';

@Component({
  selector: 'app-in-home-list',
  templateUrl: './in-home-list.component.html',
  styleUrls: ['./in-home-list.component.css']
})
export class InHomeListComponent implements OnInit {

  res: Food[];
  error: string;

  constructor( private api: ApiService, private http: HttpClient){
  }

  ngOnInit() {
    //this.http.get<Food[]>('https://localhost:5001/').pipe(catchError(this.api.handleError)).subscribe({next: r => this.res = r, error: e => this.error = e});
    [this.res, this.error] = this.api.getInHomeList();
  }
}

The commented line is the one that works here, but not in the service, the one not commented is what i want to properly work.

The service:

import { Injectable} from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Food } from './api-models';
import { catchError, throwError } from 'rxjs';

@Injectable({
  providedIn: 'root'
})

export class ApiService {

  private result: Food[];
  private error: string;

  constructor( private http: HttpClient) {

  }

  getInHomeList(): [Food[], string] {

    this.http.get<Food[]>('https://localhost:5001/').pipe(catchError(this.handleError)).subscribe({next: r => this.result = r, error: e => this.error = e});

    return [this.result, this.error];
  }

  handleError(error: any) {
    let errorMessage = "";
    if (error.error instanceof ErrorEvent) {
        // client-side error
        errorMessage = `Error: ${error.error.message}`;
    } else {
        // server-side error
        errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
    }
    console.log(errorMessage);
    return throwError(() => new Error(errorMessage))
  }
}

The @NgModule in app.module.ts:

@NgModule({
  declarations: [
    AppComponent,
    InHomeListComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
  ],
  providers: [
    ApiService
  ],
  bootstrap: [AppComponent]
})

I'm kinda new to Angular so maybe i'm missing something, or i didn't understand well how really Observable works.


Solution

  • What the comment above means is that the content inside of the "subscribe" method is asynchronous, which means your getInHomeList() method does not necessarily run in order it is written. Meaning, the inside of your "subscribe()" method may take 5 seconds (or more or less) before it gets a response, but your "return" line is going return right away, without data.

    The common way to have a http data-service is something like this, return the objservable itself to the component:

        public getInHomeList(): Observable<Food[]> {
            return this.http.get<Food[]>('https://localhost:5001/')
            .pipe(catchError(this.handleError));
          }
    
          handleError(error: any) {
            let errorMessage = "";
            if (error.error instanceof ErrorEvent) {
                // client-side error
                errorMessage = `Error: ${error.error.message}`;
            } else {
                // server-side error
                errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
            }
            console.log(errorMessage);
            return throwError(() => new Error(errorMessage))
          }
    

    Then subscribe to the observable and get the results in any component you need them:

    ngOnInit() {
        this.api.getInHomeList()
            .subscribe({next: r => this.res = r, error: e => this.error = e});
      }
    

    The inside of the "subscribe" method is asynchronous, so it may take longer to set the value of "R". But regardless of how long it takes to get the response, the rest of the ngOnInit() method is going to continue to run.