This pattern separates recoverable domain errors from truly unexpected ones by wrapping HTTP failures in a discriminated error subclass before they reach Angular's global ErrorHandler. Instead of a generic catch-all logging raw HttpErrorResponse objects, catchError translates transport-level failures into typed HttpDomainError instances carrying a narrow union of known codes. The global handler then uses instanceof to branch: domain errors get structured, actionable logging, while anything else falls through to a catch-all path, making silent misclassification of errors impossible at compile time.
import { ErrorHandler, Injectable, inject } from '@angular/core';import { catchError, throwError, Observable } from 'rxjs';class HttpDomainError extends Error {constructor(public readonly code: 'NOT_FOUND' | 'CONFLICT' | 'SERVER', message: string) {super(message);this.name = 'HttpDomainError';}}@Injectable()class GlobalErrorHandler implements ErrorHandler {handleError(error: unknown): void {if (error instanceof HttpDomainError) {console.error(`[domain:${error.code}]`, error.message);return;}console.error('[unhandled]', error);}}function loadOrder$(id: string, http: { get: (u: string) => Observable<unknown> }) {return http.get(`/orders/${id}`).pipe(catchError((err: { status?: number }) => {const code = err.status === 404 ? 'NOT_FOUND' : err.status === 409 ? 'CONFLICT' : 'SERVER';return throwError(() => new HttpDomainError(code, `Order ${id} failed`));}));}