Skip to content

Signal forms: make it easier to react to user changes #68736

@jnizet

Description

@jnizet

Which @angular/* package(s) are relevant/related to the feature request?

Forms

Description

Let's say you have a form that you populate based on an input. When the user changes a value inside a form, you want to do something (like update other form fields, or emit from an output).

My current solution is the following one, but I find it a bit convoluted:

  readonly option = input.required<string>();
  readonly formValue = linkedSignal<{ option: string; }>({ option: this.option() });
  readonly form = form(this.formValue);

  constructor() {
    // reset the form when the input changes, so that it goes back to pristine
    effect(() => {
      option();
      this.form().reset();
    });
   
    // do something when the *user* chooses another option
    effect(() => {
      if (this.form.option().dirty()) {
        doSomething(this.form.option().value());
      }
    }
  }

Proposed solution

I discussed this issue with @alxhub on discord (who asked me to open this issue).
He suggested adding a user output to the FormField directive, allowing doing something like this instead, which is much clearer:

<input [formField]="form.option" (user)="doSomething()" />

Alternatives considered

The proposed solution has limitations though:

  • it requires doing things from the template rather than doing it from the TypeScript code only
  • it makes it hard to react to any change inside a group or array or fields

For example, it we want to do something when any of the fields of an address changes, we would need to do

<input [formField]="form.address.street" (user)="doSomething()"/>
<input [formField]="form.address.postalCode" (user)="doSomething()"/>
<input [formField]="form.address.city" (user)="doSomething()"/>
<input [formField]="form.address.country" (user)="doSomething()"/>

An alternative, allowing to do things only on the TypeSript side, would be to make the FormField directive delegate its event handling to the FieldTree to which it's bound, allowing something like this:

form = form(formValue, path => {
  onUserEvent(path.option, ({ value }) => doSomethingWhenOptionChangesFromUser(value()));
});

An additional bonus (but probably harder to do) would be to make the events bubble to parent form fields, similarly to how valueChanges works in reactive forms, allowing something like

form = form(formValue, path => {
  onUserEvent(path.address, () => doSomethingWhenAnyFieldOfAddressChangesFromUser());
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: formsgemini-triagedLabel noting that an issue has been triaged by gemini

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions