javascript / expert
Snippet
Enforcing Immutable Inputs via Readonly Signal Wrappers
Typing the required input as ReadonlyArray<CartLine> with a CartLine interface whose fields are all readonly makes accidental in-place mutation a compile-time error rather than a runtime bug, which matters because signal-based change detection compares references, not deep contents. If a child accidentally called lines().push(...) on a plain array, Angular would never detect the change since the array reference stays the same, silently desyncing the UI from the actual cart state. Declaring both the array and its elements immutable forces callers to always produce a new array on update, which is exactly what triggers computed() to recalculate.
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
import { Component, input, computed, Signal } from '@angular/core';interface CartLine {readonly sku: string;readonly qty: number;readonly price: number;}@Component({selector: 'app-cart-summary',standalone: true,template: `<p>Total: {{ total() | currency }}</p>`,})export class CartSummaryComponent {lines = input.required<ReadonlyArray<CartLine>>();protected readonly total: Signal<number> = computed(() =>this.lines().reduce((sum, line) => sum + line.qty * line.price, 0));protected readonly isEmpty: Signal<boolean> = computed(() => this.lines().length === 0);}
angular
Breakdown
1
interface CartLine { readonly sku: string; readonly qty: number; readonly price: number; }
Marks every field readonly so TypeScript rejects any code path that reassigns a property on an existing line item.
2
lines = input.required<ReadonlyArray<CartLine>>();
Types the input signal as a read-only array, blocking mutating array methods like push or splice at the type level.
3
this.lines().reduce((sum, line) => sum + line.qty * line.price, 0)
computed() re-runs only when the lines() signal reports a new array reference, which immutability guarantees happens on every real update.