I'm trying to migrate a simple template-driven form to use Angular Signals.
I’m on Angular 19 (standalone components, using FormsModule) and I would like to:
Keep one object for the product (product)
Bind that object directly to the form inputs
Keep fields linked: if I change the net price, the gross price should update (and vice versa), using the VAT rate.
Current (working) version with a mutable object + [(ngModel)]
import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; type Product = { name: string; vatRate: number; priceNet: number; priceGross: number; }; @Component({ selector: 'app-product-form', standalone: true, imports: [CommonModule, FormsModule], templateUrl: './product-form.component.html', }) export class ProductFormComponent { product!: Product; ngOnInit(): void { this.product = { name: 'Banana', vatRate: 22, priceNet: 0, priceGross: 0, }; } save() { console.log(this.product); } calculateGrossFromNet() { this.product.priceGross = this.product.priceNet * (1 + this.product.vatRate / 100); } calculateNetFromGross() { this.product.priceNet = this.product.priceGross / (1 + this.product.vatRate / 100); } } <form (ngSubmit)="save()"> <div class="row"> <div class="col-6 col-lg-3"> <label for="productName">Name:</label> <input id="productName" name="productName" type="text" class="form-control" [(ngModel)]="product.name" /> </div> <div class="col-6 col-lg-3"> <label for="vatRate">VAT rate (%):</label> <input id="vatRate" name="vatRate" type="number" class="form-control" [(ngModel)]="product.vatRate" (keyup)="calculateGrossFromNet()" /> </div> <div class="col-6 col-lg-3"> <label for="priceNet">Price (net):</label> <input id="priceNet" name="priceNet" type="number" class="form-control" [(ngModel)]="product.priceNet" (keyup)="calculateGrossFromNet()" /> </div> <div class="col-6 col-lg-3"> <label for="priceGross">Price (gross):</label> <input id="priceGross" name="priceGross" type="number" class="form-control" [(ngModel)]="product.priceGross" (keyup)="calculateNetFromGross()" /> </div> </div> <button type="submit" class="btn btn-primary mt-3"> Save product </button> </form> This works exactly as I want: the product object is bound directly to the inputs, and the fields are linked (changing one price recalculates the other).
What I would like to do now is rewrite this using Angular Signals only.
Constraints / goals:
keep a single source of truth for the state (one `product` object if possible)
keep the fields linked: changing `priceNet` recalculates `priceGross` and the other way around, based on `vatRate`
avoid RxJS/Subjects/BehaviorSubjects – I want to use Signals as the main state mechanism
avoid effect() feedback loops where an effect writes back into the same signal that it depends on