0 Tage Serie
javascript / expert
Snippet

Tiefen-unveränderliche Tupel-Typumwandlung für Angular Signal-Selektoren

Rekursive bedingte Mapped Types in TypeScript wandeln komplexe Zustands-Interfaces und Tupel-Datentypen in tiefen-unveränderliche Strukturen um. In Angular Computed Signals garantiert diese Typ-Transformation zur Kompilierzeit, dass verschachtelte Zustände nicht direkt mutiert werden können.

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
30
31
32
33
34
35
36
export type DeepReadonly<T> = T extends (...args: any[]) => any
? T
: T extends ReadonlyArray<infer U>
? ReadonlyArray<DeepReadonly<U>>
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
import { Component, signal, computed } from '@angular/core';
interface AppState {
userPermissions: [string, ...string[]];
metadata: { config: { activeTheme: string } };
}
@Component({
selector: 'app-state-viewer',
standalone: true,
template: `<div>Theme: {{ readonlyState().metadata.config.activeTheme }}</div>`
})
export class StateViewerComponent {
private rawState = signal<AppState>({
userPermissions: ['READ', 'WRITE', 'EXECUTE'],
metadata: { config: { activeTheme: 'dark' } }
});
readonlyState = computed<DeepReadonly<AppState>>(() =>
Object.freeze(this.rawState()) as DeepReadonly<AppState>
);
attemptMutation(): void {
const state = this.readonlyState();
// TS Compiler Error: Cannot assign to 'activeTheme' because it is a read-only property
// state.metadata.config.activeTheme = 'light';
}
}
angular
Erklärung
1
export type DeepReadonly<T> = T extends (...args: any[]) => any ? T ...
Definiert einen rekursiven bedingten generischen Typen, der das readonly-Attribut auf alle verschachtelten Eigenschaften, Arrays und Tupel anwendet.
2
userPermissions: [string, ...string[]];
Deklariert einen nicht-leeren Tupel-Datentyp, der mindestens ein anfängliches String-Element erfordert.
3
readonlyState = computed<DeepReadonly<AppState>>(() => ...
Erstellt ein Angular Computed Signal, das den Zustand strikt verpackt im DeepReadonly-Typen wiedergibt.
4
// state.metadata.config.activeTheme = 'light';
Demonstriert die Verhinderung verschachtelter Objektmutationen zur Kompilierzeit durch den Mapped Type.