Angular’s 17 Interceptors Complete Tutorial | DevsDay.ru

IT-блоги Angular’s 17 Interceptors Complete Tutorial

dev.to 8 мая 2024 г. bytebantz


Angular’s HttpClient offers a powerful feature called interceptors, which act as middleware for HTTP requests and responses. In this guide, we’ll explore everything you need to know about interceptors, from basic concepts to advanced techniques.

Understanding Interceptors

Interceptors in HttpClient are functions or classes that can intercept outgoing HTTP requests and incoming responses. Interceptors act as middleware for HTTP requests and responses.

Interceptors are like helpers for handling HTTP requests and responses in Angular.

Common use cases for interceptors

  • Adding authentication headers
  • Retrying failed requests
  • Caching responses
  • Measuring server response times and log them
  • Driving UI elements such as a loading spinner while network operations are in progress

Types of Interceptors

HttpClient supports two main types of interceptors:

· Functional Interceptors

· DI-based Interceptors

Both types of interceptors have access to the outgoing request and can modify it before it is sent. They can also intercept the response before it reaches the application code.

1. Functional Interceptors:

These are functions that accept the outgoing request and a next function representing the next step in the interceptor chain. They are preferred for their predictable behavior.

Defining a Functional Interceptor
This interceptor logs the outgoing request URL before forwarding it to the next step in the chain and handles errors.

export const loggingInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  console.log('Request URL: ' + req.url);
  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      console.error('Logging Interceptor Functional Error:', error);
      return throwError(()=> error);
    })
  );
}

Configuring Functional Interceptors

Functional Interceptors are configured during HttpClient setup using the withInterceptors feature:

bootstrapApplication(AppComponent, {providers: [
  provideHttpClient(
    withInterceptors([loggingInterceptor, cachingInterceptor]),
  )
]});

Here, we configure both loggingInterceptor and cachingInterceptor. They will form a chain where loggingInterceptor processes the request before cachingInterceptor.

Intercepting Response Events:

Interceptors can transform the stream of HttpEvents returned by next, allowing access to or manipulation of the response.

export const loggingInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  console.log('Request URL: ' + req.url);
  return next(req).pipe(tap(event => {
    if (event.type === HttpEventType.Response) {
      console.log(req.url, 'returned a response with status', event.status);
    }
  }));
}

2. DI-based Interceptors:

These are injectable classes that implement the HttpInterceptor interface. They offer similar capabilities to functional interceptors but are configured differently through Angular’s Dependency Injection system.

Defining a DI-based Interceptor
DI-based interceptors are defined as injectable classes implementing the HttpInterceptor interface.

This interceptor logs the outgoing request URL before forwarding it to the next step in the chain.

@Injectable()
export class LoggingInterceptorDI implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log('Request URL: ' + req.url);
    return next.handle(req).pipe(
      catchError((error: HttpErrorResponse) => {
        console.error('Logging Interceptor DI Error:', error);
        return throwError(()=> error);
      })
    );
  }
}

Configuring DI-based Interceptors
DI-based interceptors are configured through Angular’s Dependency Injection system:

bootstrapApplication(AppComponent, {providers: [
  provideHttpClient(
    withInterceptorsFromDi(),
  ),
  {provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true},
]});

Example

Now let’s create a simple Angular project where we’ll implement both functional and DI-based interceptors

This simplified example covers all the functionalities mentioned: adding authentication headers, retrying failed requests, measuring server response times, loading spinner while network operations are in progress and logging.

Run the following command to generate a new project:

ng new interceptors-demo

Run the following command to generate a new service:

ng generate service auth

Now, let’s modify the auth.service.ts file in the src/app directory to manage authentication and retrieve the authentication token

// auth.service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class AuthService {
  getAuthToken(): string {
    // Logic to retrieve authentication token
    return 'your_auth_token_here';
  }
}

Run the following command to generate a new service:

ng generate service loading

Now, let’s modify the loading.service.ts file in the src/app directory to show and hide the loading spinner UI element.

import { Injectable } from '@angular/core';

@Injectable()
export class LoadingService {
  private loading = false;

  showLoadingSpinner() {
    this.loading = true;
    console.log('Loading spinner shown'); // Log when loading spinner is shown
    // Logic to show loading spinner UI element
  }

  hideLoadingSpinner() {
    this.loading = false;
    console.log('Loading spinner hidden'); // Log when loading spinner is hidden
    // Logic to hide loading spinner UI element
  }

  isLoading(): boolean {
    return this.loading;
  }
}

Run the following command to generate a new interceptor:

ng generate interceptor functional

Now, let’s modify the functional.intercepor.ts file in the src/app directory to implement functional interceptors:

import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { throwError } from 'rxjs';
import { catchError, finalize, retry } from 'rxjs/operators';
import { LoadingService } from './loading.service';

// Server Response Time Interceptor
export const responseTimeInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  const startTime = Date.now();
  return next(req).pipe(
    finalize(() => {
      const endTime = Date.now();
      const responseTime = endTime - startTime;
      console.log(`Request to ${req.url} took ${responseTime}ms`);      
    })
  );
}

// Loading Spinner Interceptor
export const loadingSpinnerInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  const loadingService = new LoadingService(); // Instantiate the loading service
  loadingService.showLoadingSpinner(); // Show loading spinner UI element

  return next(req).pipe(
    finalize(() => {
      loadingService.hideLoadingSpinner(); // Hide loading spinner UI element
    })
  );
};

export const authInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  const authToken = 'YOUR_AUTH_TOKEN_HERE';

  // Clone the request and add the authorization header
  const authReq = req.clone({
    setHeaders: {
      Authorization: `Bearer ${authToken}`
    }
  });

  // Pass the cloned request with the updated header to the next handler
  return next(authReq);
};

export const retryInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  const maxRetries = 3;

  return next(req).pipe(
    retry(maxRetries),
    catchError((error: HttpErrorResponse) => {
      console.error('Retry Interceptor Functional Error:', error);
      return throwError(()=> error);
    })
  );
};


export const loggingInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  console.log('Request URL: ' + req.url);
  return next(req);
}

Run the following command to generate a new interceptor:

ng generate interceptor dibased

Now, let’s modify the dibased.intercepor.ts file in the src/app directory to implement DI-based interceptors:

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, finalize, retry, tap } from 'rxjs/operators';
import { LoadingService } from './loading.service';
import { AuthService } from './auth.service';

@Injectable()
export class ResponseTimeInterceptorDI implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const startTime = Date.now();
    return next.handle(req).pipe(
      tap(event => {
        if (event instanceof HttpResponse) {
          const endTime = Date.now();
          const responseTime = endTime - startTime;
          console.log(`Request to ${req.url} took ${responseTime}ms`);
        }
      })
    );
  }
}

@Injectable()
export class LoadingSpinnerInterceptorDI implements HttpInterceptor {
  constructor(private loadingService: LoadingService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    this.loadingService.showLoadingSpinner(); // Show loading spinner UI element here
    return next.handle(req).pipe(
      finalize(() => {
        this.loadingService.hideLoadingSpinner(); // Hide loading spinner UI element
      })
    );
  }
}

@Injectable()
export class RetryInterceptorDI implements HttpInterceptor {
  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const maxRetries = 3; // Customize max retry attempts here
    return next.handle(req).pipe(
      retry(maxRetries),
      catchError((error: HttpErrorResponse) => {
        console.error('Retry Interceptor DI Error:', error);
        return throwError(()=> error);
      })
    );
  }
}

@Injectable()
export class AuthInterceptorDI implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authToken = this.authService.getAuthToken();
    const authReq = req.clone({
      headers: req.headers.set('Authorization', `Bearer ${authToken}`)
    });
    return next.handle(authReq);
  }
}

@Injectable()
export class LoggingInterceptorDI implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log('Request URL: ' + req.url);
    return next.handle(req);
  }
}

Now, we’ll configure both interceptors in the app.config.ts

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptors, withInterceptorsFromDi } from '@angular/common/http';
import { authInterceptorFunctional, loadingSpinnerInterceptorFunctional, loggingInterceptorFunctional, responseTimeInterceptorFunctional, retryInterceptorFunctional } from './functional.interceptor';
import { AuthInterceptorDI, LoadingSpinnerInterceptorDI, LoggingInterceptorDI, ResponseTimeInterceptorDI, RetryInterceptorDI } from './dibased.interceptor';
import { LoadingService } from './loading.service';
import { AuthService } from './auth.service';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(
      withInterceptors([
        responseTimeInterceptorFunctional,
        loadingSpinnerInterceptorFunctional, 
        authInterceptorFunctional, 
        retryInterceptorFunctional, 
        loggingInterceptorFunctional, 
      ]),
      /* THE COMMENTED CONFIGURATIONS ARE FOR DI-based INTERCEPTORS 
          Comment withInterceptors() and uncomment the below code to use DI-based interceptors
      */

      //withInterceptorsFromDi(),
    ),
    //LoadingService,
    //AuthService,
    // { provide: HTTP_INTERCEPTORS, useClass: ResponseTimeInterceptorDI, multi: true },
    // { provide: HTTP_INTERCEPTORS, useClass: LoadingSpinnerInterceptorDI, multi: true },
    // { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorDI, multi: true },
    // { provide: HTTP_INTERCEPTORS, useClass: RetryInterceptorDI, multi: true },
    // { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptorDI, multi: true }
  ]
};

Now, let’s modify the app.component.ts file to use Angular’s HttpClient to fetch data:

import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HttpClient } from '@angular/common/http';


@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent implements OnInit{
  constructor(private http: HttpClient) { }

  ngOnInit() {
    this.getData();
  }

  getData() {
    this.http.get('https://jsonplaceholder.typicode.com/posts').subscribe({
      next: data => { 
        console.log(data);
      },
      error: error => {
        console.error('Error getting post:', error);
      }
    });
  }
}
Finally, let’s update the app.component.html file to remove the default content:

<div>
  <h1>Welcome to Angular Interceptors Demo</h1>
</div>

Now, you can run the application using the following command:

ng serve

This will start a development server, and you should be able to see the output in the browser console.

Conclusion

Interceptors are powerful tools for managing HTTP traffic within Angular applications. By intercepting requests and responses, developers can implement a wide range of functionalities, from authentication and caching to logging and error handling. Understanding and effectively utilizing interceptors can significantly enhance the reliability, maintainability, security, and performance of Angular applications.

To get the whole code, check the repo below👇👇👇
https://github.com/anthony-kigotho/interceptors-demo

Источник: dev.to

Наш сайт является информационным посредником. Сообщить о нарушении авторских прав.

angular angular17 frontend