0 Tage Serie
javascript / expert
Snippet

Diskriminierte Routendaten durch Resolver-gestütztes Type-Narrowing

Ein Resolver, der bei 403 oder 404 wirft, zwingt jede konsumierende Komponente auf eine generische Router-Fehlerseite und verwirft dabei den spezifischen Grund, aus dem die Navigation anders behandelt werden sollte. Stattdessen eine diskriminierte Union zurückzugeben lässt `ActivatedRoute.data` ein typisiertes `kind`-Feld tragen, über das das Komponenten-Template per `@switch` verzweigen kann, sodass eine Paywall-Meldung und eine Not-found-Meldung ohne zweiten Roundtrip getrennt bleiben. Das tauscht die eingebaute Fehlerbehandlungs-Pipeline des Routers gegen explizites, erschöpfend geprüftes Branching in der Komponente — der Resolver lehnt nie ab, sodass `withComponentInputBinding` oder ein Route-Guard den Fehlergrund nicht versehentlich verschlucken kann.

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 { ResolveFn } from '@angular/router';
import { inject } from '@angular/core';
type LoadResult =
| { kind: 'ok'; payload: { id: string; title: string } }
| { kind: 'forbidden'; reason: string }
| { kind: 'missing' };
export const articleResolver: ResolveFn<LoadResult> = async (route) => {
const id = route.paramMap.get('id');
if (!id) {
return { kind: 'missing' };
}
const service = inject(ArticleGateway);
const res = await service.fetchRaw(id);
if (res.status === 403) {
return { kind: 'forbidden', reason: 'not-subscribed' };
}
if (res.status === 404) {
return { kind: 'missing' };
}
return { kind: 'ok', payload: res.body };
};
declare class ArticleGateway {
fetchRaw(id: string): Promise<{ status: number; body: { id: string; title: string } }>;
}
angular
Erklärung
1
type LoadResult = { kind: 'ok'; ... } | { kind: 'forbidden'; ... } | { kind: 'missing' };
Das gemeinsame Literalfeld `kind` nutzt TypeScript, um die Union innerhalb eines `@switch`-Blocks im konsumierenden Template einzuschränken, ohne eine manuelle Type-Guard-Funktion.
2
if (res.status === 403) { return { kind: 'forbidden', reason: 'not-subscribed' }; }
Wandelt einen HTTP-Status in Domänendaten statt in eine Exception um und hält damit das Promise des Resolvers stets erfüllt, sodass die `Router`-Navigation immer abschließt.
3
export const articleResolver: ResolveFn<LoadResult> = async (route) => {
Der funktionale `ResolveFn` (Ersatz für das veraltete klassenbasierte `Resolve`-Interface) typisiert seinen Rückgabewert direkt als Union, sodass `route.snapshot.data['article']` durchgängig typisiert ist.