Dev.to WebDev 🛠 Dev 👁 1 📖 3 min read

Dynamic Form Validation with Angular Signal Forms

There are times when we might need to work on forms that show and validate fields depending on a users selected input. For example an account registration form, with two types of account; Individual Business When the

There are times when we might need to work on forms that show and validate fields depending on a users selected input. For example an account registration form, with two types of account;

  • Individual
  • Business

When the user selects individual, show:

  • First name
  • Last name

When the user selects business, show:

  • Company name
  • Tax ID

This means that the validation rules will likely change for every change in account type.

In this blog post, we'll see how we can dynamically validate form fields based on a users selected account type, using angular signal forms.

Setting up our form model

We define the shape of the form like so:

export interface userAccount {
  account: {
    type: 'individual' | 'business';
    individual: {
      firstName: string;
      lastName: string;
    },
    business: {
      companyName: string;
      taxId: string;
      registrationNumber: string;
    }
  }
}

Creating the accountModel

According to other angular docs, when working with dynamic fields, it's a good idea to initialize every field, even if some of them won't be visible initially.

  accountModel = signal<userAccount>({
    account: {
      type: 'individual',
      individual: {
        firstName: '',
        lastName: '',
      },
      business: {
        companyName: '',
        taxId: '',
        registrationNumber: '',
      },
    },
  });

Defining our form schema

Next, we'll tell the form which sections should be visible.

When the user selects Individual, we want to hide the business fields.

When the user selects Business, we want to hide the individual fields.

  accountTypeSchema = schema<userAccount>((user) => {
    // Hide individual account when user selects business.
    hidden(user.account.individual, {
      when: ({ valueOf }) => valueOf(user.account.type) === 'business',
    });
    // Hide business account when user selects individual.
    hidden(user.account.business, {
      when: ({ valueOf }) => valueOf(user.account.type) === 'individual',
    });
  });

Creating our form

Now that we have both our schema and model, we can create the form.
To create the form, we use Angular Signal's form() function, passing both accountModel and accountTypeSchema as arguments.

  registerationForm = form<userAccount>(this.accountModel, this.accountTypeSchema);

At this point, our form is ready to be used in the template.

Building the template

Since our schema already knows which part of the form should be hidden based on a users selected input, we just need to reflect that in the template of our application.
Angular signal forms, expects hidden fields to be removed from the DOM. And that's why we wrapped each section with and @if block statement.
Instead of checking the selected account type, we simply check if the section is hidden.

<form>
  <h2>Create Account</h2>
  <div>
    <label>Account Type</label>

    <div>
      <label>
        <input
          type="radio"
          value="individual"
          [formField]="registerationForm.account.type"
        />
        Individual
      </label>

      <label>
        <input
          type="radio"
          value="business"
          [formField]="registerationForm.account.type"
        />
        Business
      </label>
    </div>
  </div>

  @if (!registerationForm.account.individual().hidden()) {
    <fieldset>
      <legend>Individual Information</legend>

      <div>
        <label for="firstName">First Name</label>
        <input
          id="firstName"
          type="text"
          [formField]="registerationForm.account.individual.firstName"
        />
      </div>

      <div>
        <label for="lastName">Last Name</label>
        <input
          id="lastName"
          type="text"
          [formField]="registerationForm.account.individual.lastName"
        />
      </div>
    </fieldset>
  }

  @if (!registerationForm.account.business().hidden()) {
    <fieldset>
      <legend>Business Information</legend>

      <div>
        <label for="companyName">Company Name</label>
        <input
          id="companyName"
          type="text"
          [formField]="registerationForm.account.business.companyName"
        />
      </div>

      <div>
        <label for="taxId">Tax ID</label>
        <input
          id="taxId"
          type="text"
          [formField]="registerationForm.account.business.taxId"
        />
      </div>

      <div>
        <label for="registrationNumber">Registration Number</label>
        <input
          id="registrationNumber"
          type="text"
          [formField]="registerationForm.account.business.registrationNumber"
        />
      </div>
    </fieldset>
  }

  <button type="submit">Create Account</button>
</form>

When you run the application, you can see that switching between individual and business types automatically updates the visible section of the form with no data lost.

🔗 Live Demo & Source Code ⭐

Dynamic Form Validation with Angular Signal Forms

References

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.