All files / http error-notification.interceptor.ts

96.29% Statements 26/27
90% Branches 9/10
100% Functions 5/5
95.83% Lines 23/24

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 951x 1x 1x 1x               1x 1x 1x                                                   1x 10x 10x     10x   9x 9x       9x             9x 1x       8x 8x 1x       7x 7x   7x                                 1x          
import { SKIP_ERROR_NOTIFICATION, CUSTOM_ERROR_MESSAGE, SKIP_ERROR_FOR_STATUSES } from './http-context-tokens';
import { HttpErrorMapperService } from './http-error-mapper.service';
import { NotificationService } from '../services/notification.service';
import {
    HttpInterceptor,
    HttpRequest,
    HttpHandler,
    HttpEvent,
    HttpErrorResponse,
    HTTP_INTERCEPTORS,
} from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
 
/**
 * HTTP interceptor that automatically shows toast notifications for HTTP errors.
 *
 * Behavior can be controlled per-request using HTTP context tokens:
 *
 * @example
 * // Skip error notification entirely
 * this.http.get('/api/data', {
 *   context: new HttpContext().set(SKIP_ERROR_NOTIFICATION, true)
 * });
 *
 * @example
 * // Skip only for specific status codes
 * this.http.get('/api/data', {
 *   context: new HttpContext().set(SKIP_ERROR_FOR_STATUSES, [404])
 * });
 *
 * @example
 * // Custom error message
 * this.http.post('/api/action', body, {
 *   context: new HttpContext().set(CUSTOM_ERROR_MESSAGE, 'Failed to save')
 * });
 */
@Injectable()
export class ErrorNotificationInterceptor implements HttpInterceptor {
    private readonly notification = inject(NotificationService);
    private readonly errorMapper = inject(HttpErrorMapperService);
 
    intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
        return next.handle(request).pipe(
            catchError((error: unknown) => {
                if (error instanceof HttpErrorResponse) {
                    this.handleError(request, error);
                } else E{
                    this.notification.error('Произошла неожиданная ошибка.');
                }
                return throwError(() => error);
            })
        );
    }
 
    private handleError(request: HttpRequest<unknown>, error: HttpErrorResponse): void {
        // Check if notifications are completely skipped for this request
        if (request.context.get(SKIP_ERROR_NOTIFICATION)) {
            return;
        }
 
        // Check if this specific status code should be skipped
        const skipStatuses = request.context.get(SKIP_ERROR_FOR_STATUSES);
        if (skipStatuses.length > 0 && skipStatuses.includes(error.status)) {
            return;
        }
 
        // Determine the message to show
        const customMessage = request.context.get(CUSTOM_ERROR_MESSAGE);
        const message = customMessage || this.errorMapper.mapError(error).message;
 
        this.notification.error(message);
    }
}
 
/**
 * Provider for ErrorNotificationInterceptor.
 * Add this to your app providers to enable automatic error notifications.
 *
 * @example
 * // app.config.ts
 * export const appConfig: ApplicationConfig = {
 *   providers: [
 *     provideHttpClient(withInterceptorsFromDi()),
 *     errorNotificationInterceptorProvider,
 *   ],
 * };
 */
export const errorNotificationInterceptorProvider = {
    provide: HTTP_INTERCEPTORS,
    useClass: ErrorNotificationInterceptor,
    multi: true,
};