angularformsangular-signalsangular-zoneless

Angular forms with signals (making it ready for zoneless change detection)


A lot of my existing Angular code using template-driven forms looks something like this:

Component (*.ts):

protected person: Person;

Template (*.html):

<input type="text" [(ngModel)]="person.firstName">
<input type="text" [(ngModel)]="person.lastName">

Now let's say I'd like to make such an application ready for zoneless Angular. The obvious approach would be using signals. However, the following does not work, as signals are supposed to be immutable:

Component (*.ts):

protected person = model<Person>({ firstName: '', lastName: '' });

Template (*.html):

<input type="text" [(ngModel)]="person().firstName">
<input type="text" [(ngModel)]="person().lastName">

Question:

What's the best practice with modern Angular to bind a form to an object in such a way that it will be compatible with zoneless change detection?


Solution

  • Signals for forms is a part of the Angular Roadmap this will be the ultimate solution to this scenario. But we have to wait for that to happen.

    Signal Forms

    We plan to analyze existing feedback about Angular forms and design a solution which addresses developers' requirements and uses Signals for management of reactive state.


    Until then try the below approach:

    You cannot directly bind the values (since that can be done only for the model and not the inner values), but you can set a (ngModelChange) that will update the inner property using update and then use Object Destructuring so that a new memory reference is created and the signal detects it as a change.

    <input type="text" [ngModel]="person().firstName" (ngModelChange)="updateProp($event, 'firstName')">
    <input type="text" [ngModel]="person().lastName" (ngModelChange)="updateProp($event, 'lastName')">
    

    The TS Code will look like:

    updateProp(value: any, propName: string) {
      this.person.update((prevPerson: Person) => ({
        [propName]: value,
        ...prevPerson,
      });
    }
    

    There are other solutions using effect or linkedSignal, but they involve setting up individual properties and a lot of extra initialization code, so I am recommending this approach.