angularrxjs

Initial value of Observable is not used


I tried to make a HttpWrapper service to wrap the return object with a wrapper with loading value to load a spinner. At first, I initialize the Observable with loading: true but it didn't work. The observable looks like null or undefined until get updated when API call is completed.

The wrapper service:

import { inject, Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { catchError, delay, map, Observable, of, switchMap } from 'rxjs';

export interface ApiResponse<T> {
  data: T | null;
  loading: boolean;
  error?: Error | null;
}

@Injectable({
  providedIn: 'root',
})
export class HttpWrapperService {
  #httpClient = inject(HttpClient);

  get<T>(url: string, params?: HttpParams): Observable<ApiResponse<T>> {
    return of({ data: null, loading: true, error: null }).pipe(
      delay(5000),
      switchMap(() =>
        this.#httpClient.get<T>(url, { params, withCredentials: true }).pipe(
          map(
            (data) => ({ data, loading: false, error: null } as ApiResponse<T>)
          ),
          catchError((error) =>
            of({ data: null, loading: false, error } as ApiResponse<T>)
          )
        )
      )
    );
  }
}

The template:

<span>Loading: {{(dataObservable | async)?.loading}}</span>
#httpWrapper = inject(HttpWrapperService);

  dataObservable: Observable<ApiResponse<any>> = of({
    data: null,
    loading: true,
    error: null,
  });

  constructor() {
    this.dataObservable = this.#httpWrapper.get(
      'https://jsonplaceholder.typicode.com/todos/1'
    );
  }

I tried to init the dataObservable with an Observable but it didn't work either. The loading value is undefined and change to false when the call is finished.

Here is a simple repo:

https://stackblitz.com/edit/stackblitz-starters-4jyxfj5l?file=src%2Fmain.ts


Solution

  • You're close. You need to use startsWith like below:

    get<T>(url: string, params?: HttpParams): Observable<ApiResponse<T>> {
      const initialVal = { data: null, loading: true, error: null };
    
      return of(initialVal).pipe(
        delay(5000),
        switchMap(() => this.#httpClient.get<T>(url, { params, withCredentials: true }).pipe(
          map(data => ({ data, loading: false, error: null } as ApiResponse<T>)),
          catchError((error) => of({ data: null, loading: false, error } as ApiResponse<T>))
        )),
        startWith(initialVal)
      );
    }