Evolution in Form Validators: Goodbye customError, Hello Plain Objects


Form management in Angular, especially with the arrival of Signal-based forms, is constantly evolving to improve ergonomics and the overall developer experience (DX).

A subtle but incredibly welcome change is the simplification in how we define custom validation errors.

What previously required a utility function like customError to wrap our error object, now allows us to return a plain JavaScript object (POJO) directly.

This change reduces boilerplate, simplifies the API, and makes creating validators much more intuitive.

💡 Note: The form() and validate() APIs discussed here are part of the new Signal-based forms system.

You can explore the implementation details in the Angular PR:

👉 PR #64339 — Simplify Custom Errors




🔑 The Key Change: Before and After

Let’s look at a direct comparison — the core of this improvement.



Before 👎

Previously, to indicate a custom validation error, we often needed to import and use a utility function to wrap our error and ensure it was correctly typed and recognized by the forms system.

import { customError } from 'some-forms-library';

const cat = signal('meow');

const f = form(
  cat,
  (p) => {
    validate(p, () => {
      // We needed the customError wrapper
      if (p() === 'meow') {
        return customError({ kind: 'i am a custom error' });
      }
      return null;
    });
  });
Enter fullscreen mode

Exit fullscreen mode

With the new simplified API, the validation engine is smart enough to understand a simple object as an error response.

If the validator function returns an object, it’s an error.

If it returns null or undefined, it’s valid.

const cat = signal('meow');

const f = form(
  cat,
  (p) => {
    validate(p, () => {
      // We simply return the error object
      if (p() === 'meow') {
        return { kind: 'iAmACustomError' }; // Much cleaner!
      }
      return null; // Valid
    });
  });
Enter fullscreen mode

Exit fullscreen mode




💡 Why Does This Change Matter?



🚫 Less Boilerplate

You no longer need to import customError in every validator file.



🧠 More Intuitive API

The flow feels natural — “Is there an error? If so, return the error object.”



🧪 Easier Testing

Mocking or stubbing a validator is trivial when it just returns an object.



⚙️ Consistency

It aligns with how synchronous validators work in traditional reactive forms (Validators.required returns { required: true }).




🎯 Final Thoughts

While this might seem like a small change, it’s a perfect example of Angular’s ongoing effort to make APIs simpler, more consistent, and more developer-friendly.

Cleaner validators mean fewer imports, less friction, and a smoother DX overall — especially as Signal-based forms continue to mature.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *