Search code examples
angularangular-materialangular-http-interceptors

How to Implement a Loading Spinner for HTTP Calls in Angular 17?


I am working on an Angular 17 project and need to implement a loading spinner that displays during HTTP calls. I want the spinner to show when a request or several is made and hide once the response is received. I am looking for a clean and efficient way to achieve this.

Here is what I have so far: Demo@Stackblitz

app.component.html

@if (loading$ | async) {
  <ng-container>
    <div class="overlay"></div>
    <app-spinner></app-spinner>
  </ng-container>
}
<router-outlet></router-outlet>

app.component.ts

export class AppComponent {
  loading$ = this.loadingService.loading$;

  constructor(private loadingService: LoadingService) {}
}

main.ts

bootstrapApplication(AppComponent, appConfig)
  .catch((err) => console.error(err));

app.config.ts

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withInterceptors([networkInterceptorFn])),provideRouter(routes), provideAnimations(), provideAnimationsAsync(), ]
};

network.interceptor.ts

export const networkInterceptorFn: HttpInterceptorFn = (req, next) => {
  let totalRequests = 0;
  let requestsCompleted = 0;

  const loadingService = inject(LoadingService);

  loadingService.show();
  totalRequests++;

  return next(req).pipe(
    finalize(() => {
      requestsCompleted++;

      if (requestsCompleted === totalRequests) {
        loadingService.hide();
        totalRequests = 0;
        requestsCompleted = 0;
      }
    })
  );
};

Function for testing the loading spinner:

  constructor(private taskHttpService: TaskHttpService, private http: HttpClient) {
    this.fetchDataTmp();
  }

  fetchDataTmp(): Observable<any>{
    return this.http.get('https://jsonplaceholder.typicode.com/todos/1').pipe(
      delay(5000)
    );
  }

Solution

  • You are not registering your NetworkInterceptor into the application

    Approach 1: Working with traditional HttpInterceptor class.

    import {
      HTTP_INTERCEPTORS,
      HttpClient,
      provideHttpClient,
      withInterceptorsFromDi,
    } from '@angular/common/http';
    
    import {
      networkInterceptorFn
    } from './app/network/network.interceptor';
    
    bootstrapApplication(App, {
      providers: [
        provideHttpClient(withInterceptorsFromDi()),
        {
          provide: HTTP_INTERCEPTORS,
          useClass: NetworkInterceptor,
          multi: true,
        },
      ],
    });
    

    Approach 2: Working with HttpInterceptorFn.

    Migrate the legacy HttpInterceptor class to HttpInterceptorFn.

    export const networkInterceptorFn: HttpInterceptorFn = (req, next) => {
      let totalRequests = 0;
      let requestsCompleted = 0;
    
      const loadingService = inject(LoadingService);
    
      loadingService.show();
      totalRequests++;
    
      return next(req).pipe(
        finalize(() => {
          requestsCompleted++;
    
          console.log(requestsCompleted, totalRequests);
    
          if (requestsCompleted === totalRequests) {
            loadingService.hide();
            totalRequests = 0;
            requestsCompleted = 0;
          }
        })
      );
    };
    
    import {
      HttpClient,
      provideHttpClient,
      withInterceptors
    } from '@angular/common/http';
    import {
      networkInterceptorFn,
    } from './app/network/network.interceptor';
    
    bootstrapApplication(App, {
      providers: [
        provideHttpClient(
          withInterceptors([networkInterceptorFn])
        ),
      ],
    });
    

    Demo @ StackBlitz

    References:

    1. The Refurbished HttpClient in Angular 15 – Standalone APIs and Functional Interceptors

    2. Angular 17: HTTP Interceptors guide