Home > Software engineering >  Why Angular 14 async http client call doesn't await to get response?
Why Angular 14 async http client call doesn't await to get response?

Time:12-18

I have a problem with angular 14 async / await that is not working correctly here is my service that calls a "HttpClient" get request that should be async:

export class ShopService {
  DYNAMIC_PROPERTY_GETALL_SELECT_ITEM_URL = environment.BASE_URL   'DynamicProperty/GetAllSelecItem';

  constructor(private httpClient: HttpClient) {

  }

  async getAllDynamicProperties(): Promise<ApiResultModelDTO<SelectItemDTO>> {
    var result: ApiResultModelDTO<SelectItemDTO> = ApiResultModelDTO.GetNullModel<SelectItemDTO>();
    this.httpClient.get<ApiResultModelDTO<SelectItemDTO>>(this.DYNAMIC_PROPERTY_GETALL_SELECT_ITEM_URL)
      .subscribe({
        next: async (response: ApiResultModelDTO<SelectItemDTO>) => {
          result = response;
        },
        error: async () => {
          result = ApiResultModelDTO.GetNullModel<SelectItemDTO>();
        }
      });
    return result;
  }
 }

and I want to use await in my component when I call this method to return my response here is my component call method :

  async loadDynamicProperties() {
    var result = await this.shopService.getAllDynamicProperties();
  }

but this await in "loadDynamicProperties" not working and my result is not the response I get in actual http call.

Why is not working?! What I'm missing?

CodePudding user response:

In order to convert your observable to a promise you can leverage the firstValueFrom or lastValueFrom operators.

See guide: https://obaranovskyi.medium.com/the-ultimate-guide-to-observables-and-vs-promises-rxjs7-296877376668

async getAllDynamicProperties(): Promise<ApiResultModelDTO<SelectItemDTO>> {
  const getItems$: Observable<ApiResultModelDTO<SelectItemDTO>> =
    this.httpClient.get<ApiResultModelDTO<SelectItemDTO>>(this.DYNAMIC_PROPERTY_GETALL_SELECT_ITEM_URL)
      .pipe(
        catchError(
          () => ApiResultModelDTO.GetNullModel<SelectItemDTO>()
        )
      )

  return firstValueFrom(getItems$);
}
async loadDynamicProperties() {
  const result = await this.shopService.getAllDynamicProperties();
}

Note: This is not a recommended pattern for handling asynchronous code in Angular due to the ubiquity of RxJS and its power compared to promises (even for single value observables like HTTP requests). If your use case supports it, I would recommend working with RxJS not against it


An example of how to achieve this using RxJS:

"normal" usage, i.e. processing in subscribe code, leading to duplicate subscribe blocks

getAllDynamicProperties$(): Observable<ApiResultModelDTO<SelectItemDTO>> {
  const getItems$: Observable<ApiResultModelDTO<SelectItemDTO>> =
    this.httpClient.get<ApiResultModelDTO<SelectItemDTO>>(this.DYNAMIC_PROPERTY_GETALL_SELECT_ITEM_URL)
      .pipe(
        catchError(
          () => ApiResultModelDTO.GetNullModel<SelectItemDTO>()
        )
      )

  return getItems$;
}

loadDynamicProperties1(): void {
  this.shopService
    .getAllDynamicProperties()
    .subscribe({
      next: (dynamicProperties: ApiResultModelDTO<SelectItemDTO>) => {
        console.log(dynamicProperties) // Duplicated
      }
    });
}

loadDynamicProperties2(): void {
  this.shopService
    .getAllDynamicProperties()
    .subscribe({
      next: (dynamicProperties: ApiResultModelDTO<SelectItemDTO>) => {
        console.log(dynamicProperties) // Duplicated
      }
    });
}

Avoiding multiple duplicated subscribe blocks by leveraging the tap operator, e.g.

getAllDynamicProperties$(): Observable<ApiResultModelDTO<SelectItemDTO>> {
  const getItems$: Observable<ApiResultModelDTO<SelectItemDTO>> =
    this.httpClient.get<ApiResultModelDTO<SelectItemDTO>>(this.DYNAMIC_PROPERTY_GETALL_SELECT_ITEM_URL)
      .pipe(
        tap(
          (dynamicProperties: ApiResultModelDTO<SelectItemDTO>) => {
            console.log(dynamicProperties) // Not duplicated
          } 
        ),
        catchError(
          () => ApiResultModelDTO.GetNullModel<SelectItemDTO>()
        )
      )

  return getItems$;
}

loadDynamicProperties1(): void {
  this.shopService.getAllDynamicProperties().subscribe(); // Subscribe now only needed to trigger the HTTP request
}

loadDynamicProperties2(): void {
  this.shopService.getAllDynamicProperties().subscribe();
}

CodePudding user response:

You are doing this quite wrong,

  1. You are in angular, why are you using promises
  2. If you really want to use promises, why not simply return using .toPromise()

Basically something like


import {of, catchError} from 'rxjs';

export class ShopService {
  DYNAMIC_PROPERTY_GETALL_SELECT_ITEM_URL = environment.BASE_URL   'DynamicProperty/GetAllSelecItem';

  constructor(private httpClient: HttpClient) {

  }

  getAllDynamicProperties(): Promise<ApiResultModelDTO<SelectItemDTO>> {
    
    return this.httpClient.get<ApiResultModelDTO<SelectItemDTO>>(this.DYNAMIC_PROPERTY_GETALL_SELECT_ITEM_URL)
      .pipe(
        catchError(() => {
          var result: ApiResultModelDTO<SelectItemDTO> = 
          ApiResultModelDTO.GetNullModel<SelectItemDTO>();
          return of(result)
        })
      )
      .toPromise()
    }
 }

  • Related