javascript / expert
Snippet
Deep Immutable Tuple Type Transmutation for Angular Signal Selectors
Using recursive conditional mapped types in TypeScript converts complex state interfaces and tuple datatypes into deeply immutable structures. When consumed in Angular computed signals, this type transformation guarantees at compile time that nested array and object state slices cannot be mutated directly by consumers.
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
Breakdown
1
export type DeepReadonly<T> = T extends (...args: any[]) => any ? T ...
Defines a recursive conditional generic type that recursively applies readonly modifiers to all nested properties, arrays, and tuples.
2
userPermissions: [string, ...string[]];
Declares a non-empty tuple datatype requiring at least one initial string element.
3
readonlyState = computed<DeepReadonly<AppState>>(() => ...
Creates an Angular computed signal projecting state wrapped strictly in the DeepReadonly type envelope.
4
// state.metadata.config.activeTheme = 'light';
Demonstrates compile-time prevention of nested property mutations enforced by the deep mapped type.