The mutate method changes a writable Angular signal according to its current value. Unlike set, which replaces the value, mutate supports incremental updates such as adding, removing, or modifying items in an array. section 6.2.1
Use mutate when an update depends on the signal's existing contents:
- Add product selections by pushing or updating entries in the current cart-line array. section 6.2.1
- Remove a selection by finding its index and splicing the current array. section 6.2.1
- Populate a product signal from a data source while retaining controlled write access inside the model. section 10.4.4
Removing a cart line
A cart can locate a product and remove it inside a mutate callback: this.linesSignal.mutate(linesArray => { let index = linesArray.findIndex(l => l.product.id == id); linesArray.splice(index, 1); }); The signal's current array is changed incrementally, allowing dependent summaries to update. section 6.2.1
Listing 6.1. The contents of the cart.model.ts file in the src/app/model folder
import { Injectable, Signal, WritableSignal, computed, signal }
from "@angular/core";
import { Product } from "./product.model";
@Injectable()
export class Cart {
private linesSignal: WritableSignal<CartLine[]>;
public summary: Signal<CartSummary>;
constructor() {
this.linesSignal = signal([]);
this.summary = computed(() => {
let newSummary = new CartSummary();
this.linesSignal().forEach(l => {
newSummary.itemCount += l.quantity;
newSummary.cartPrice += l.lineTotal;
});
return newSummary;
})
}
get lines(): Signal<CartLine[]> {
return this.linesSignal.asReadonly();
}
addLine(product: Product, quantity: number = 1) {
this.linesSignal.mutate(linesArray => {
let line = linesArray.find(l => l.product.id == product.id);
if (line != undefined) {
line.quantity += quantity;
} else {
linesArray.push(new CartLine(product, quantity));
}
});
}
updateQuantity(product: Product, quantity: number) {
this.linesSignal.mutate(linesArray => {
let line = linesArray.find(l => l.product.id == product.id);
if (line != undefined) {
line.quantity = Number(quantity);
}
});
}
removeLine(id: number) {
this.linesSignal.mutate(linesArray => {
let index = linesArray.findIndex(l => l.product.id == id);
linesArray.splice(index, 1);
});
}
clear() {
this.linesSignal.set([]);
}
}
export class CartLine {
constructor(public product: Product,
public quantity: number) {}
get lineTotal() {
return this.quantity * (this.product.price ?? 0);
}
}
export class CartSummary {
itemCount: number = 0;
cartPrice: number = 0;
}
Inspect the complete cart model to see mutate alongside a computed summary and a read-only public signal. section 6.2.1
Listing 10.12. Using signals in the repository.model.ts file in the src/app folder
import { Product } from "./product.model";
import { SimpleDataSource } from "./datasource.model";
import { Signal, WritableSignal, signal } from "@angular/core";
export class Model {
private dataSource: SimpleDataSource;
private products: WritableSignal<Product[]>;
private locator = (p: Product, id: number | any) => p.id == id;
constructor() {
this.dataSource = new SimpleDataSource();
this.products = signal(new Array<Product>());
//this.dataSource.getData().forEach(p => this.products.push(p));
this.products.mutate(prods =>
this.dataSource.getData().forEach(p => prods.push(p)));
}
get Products(): Signal<Product[]> {
return this.products.asReadonly();
}
getProduct(id: number): Product | undefined {
return this.products().find(p => this.locator(p, id));
}
saveProduct(product: Product) {
if (product.id == 0 || product.id == undefined) {
product.id = this.generateID();
this.products.mutate(prods => prods.push(product));
} else {
this.products.mutate(prods => {
let index = prods.findIndex(p =>
this.locator(p, product.id));
prods.splice(index, 1, product);
});
}
}
deleteProduct(id: number) {
this.products.mutate(prods => {
let index = prods.findIndex(p => this.locator(p, id));
if (index > -1) {
prods.splice(index, 1);
}
});
}
private generateID(): number {
let candidate = 100;
while (this.getProduct(candidate) != null) {
candidate++;
}
return candidate;
}
}
Compare repository initialization with mutate and the private writable signal exposed through asReadonly. section 10.4.4
