0 day streak
javascript / expert
Snippet

Typed Error Boundaries with Angular's ErrorHandler and RxJS catchError Chains

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.

snippet.js
javascript
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
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`));
})
);
}
angular
Breakdown
1
class HttpDomainError extends Error {
Subclassing Error preserves stack traces while adding a discriminant field for exhaustive handling.
2
constructor(public readonly code: 'NOT_FOUND' | 'CONFLICT' | 'SERVER', message: string) {
The code parameter is a closed union, so downstream branching can be checked exhaustively by the compiler.
3
handleError(error: unknown): void {
Typing the parameter as unknown forces a runtime type check before any property access, avoiding unsafe assumptions.
4
if (error instanceof HttpDomainError) {
instanceof narrows the type so error.code is safely accessible only inside this branch.
5
return throwError(() => new HttpDomainError(code, `Order ${id} failed`));
Wrapping in a factory function (not a bare value) matches RxJS 7+'s lazy error creation contract.