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()andvalidate()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;
    });
  });
🧩 Simplified Validator API in Angular Signal Forms
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
    });
  });
💡 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.
 
 
              
 
    
Top comments (0)