Two-Way Binding with Signals for Complex Objects in Angular Forms

Discussão em 'Angular' iniciado por Paul, Setembro 9, 2024.

  1. Paul

    Paul Guest

    I'm seeking guidance on the best practice for using two-way binding with signals, particularly for complex objects in template-driven forms. Our team extensively uses template-driven forms and appreciates the simplicity of two-way binding for inputs. We're also excited about the new "model" input, which seems tailored for this purpose.

    Currently, we use a viewModel signal to hold the state of our forms. These viewModels are typically objects, which appears to cause issues with two-way binding.

    The problem:

    1. Two-way binding on an object property doesn't update the signal.
    2. There's no straightforward way to control this without abandoning two-way binding or using separate signals for each input, which becomes cumbersome for larger forms.

    Example code: you can just paste the code here: https://angular.dev/playground

    import {
    ChangeDetectionStrategy,
    Component,
    computed,
    effect,
    signal,
    WritableSignal
    } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    import { bootstrapApplication } from '@angular/platform-browser';

    @Component({
    selector: 'app-root',
    standalone: true,
    changeDetection: ChangeDetectionStrategy.OnPush,
    imports: [FormsModule],
    template: `
    <!-- using two-way-binding with an object signal -->
    <!-- this will never update the signal "properly" -->
    <input type="text" [(ngModel)]="viewModel().name" />

    <!-- is this the recommended way? -->
    <!-- of course I could use primitive values for two-way-bindings only -->
    <!-- but with larger forms this seems not so nice -->
    <!-- especially when using multiple components with the model input -->
    <input
    type="text"
    [ngModel]="viewModel().name"
    (ngModelChange)="nameChange($event)"
    />

    <br />
    <!-- this will be updated because of the change detection that gets triggered by the input -->
    <!-- the signal never notifies, because the reference is not changed -->
    {{ viewModel().name }}

    <br />
    <button (click)="onClick()">click</button>
    computed: {{ testComputed().name }}
    `
    })
    export class CookieRecipe {
    viewModel: WritableSignal<{ name: string }> = signal({ name: 'startName' });

    testComputed = computed(() => {
    // this will not be triggered, because the reference of the signal value is not changed.
    console.warn('inside computed', this.viewModel());
    return this.viewModel();
    });

    constructor() {
    effect(() => {
    // this will not be triggered, because the reference of the signal value is not changed.
    console.warn('inside effect', this.viewModel());
    });
    }

    onClick() {
    console.warn('button clicked', this.viewModel());
    // the set here will change the reference and therefore the signal will update the effect, the computed and the view
    this.viewModel.set({ name: 'buttonClick' });
    }

    nameChange(name: string) {
    this.viewModel.set({ ...this.viewModel, name });
    }

    ngDoCheck() {
    console.warn('inside ngDoCheck');
    }
    }

    bootstrapApplication(CookieRecipe);



    Questions:

    1. Is there a recommended approach to handle this scenario?
    2. Are there any plans to address this in future Angular releases?
    3. If not, what's the best current practice for managing complex form state with signals while maintaining the convenience of two-way binding?

    Any insights or recommendations would be greatly appreciated. Thank you!

    Continue reading...

Compartilhe esta Página