Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

clydin
Copy link
Member

@clydin clydin commented Sep 8, 2025

This commit introduces a comprehensive set of 18 examples for the new signal-based forms functionality in Angular. These examples are designed to be used with the find_examples tool (MCP) to provide developers with actionable, real-world code snippets covering a wide range of use cases.

The find_examples tool is currently experimental and must be enabled using the -E find_examples option when running the ng mcp command.

The examples cover the following key concepts:

  • Core concepts and basic setup
  • Built-in and cross-field validation
  • Dynamic and conditional form structures
  • Custom form controls and reusable validators
  • Asynchronous operations and third-party integrations (Zod)
  • Best practices for testing, accessibility, and styling

@angular-robot angular-robot bot added detected: feature PR contains a feature commit area: @angular/cli labels Sep 8, 2025
@clydin clydin force-pushed the mcp/signal-form-examples branch 2 times, most recently from 93f4632 to 0c84e42 Compare September 8, 2025 13:55
@clydin clydin added the target: major This PR is targeted for the next major release label Sep 8, 2025
@clydin clydin force-pushed the mcp/signal-form-examples branch 3 times, most recently from dd1d83d to 3653ee5 Compare September 8, 2025 23:10
@clydin clydin requested a review from mmalerba September 9, 2025 12:10
This commit introduces a comprehensive set of 18 examples for the new signal-based forms functionality in Angular. These examples are designed to be used with the `find_examples` tool (MCP) to provide developers with actionable, real-world code snippets covering a wide range of use cases.

The `find_examples` tool is currently experimental and must be enabled using the `-E find_examples` option when running the `ng mcp` command.

The examples cover the following key concepts:
- Core concepts and basic setup
- Built-in and cross-field validation
- Dynamic and conditional form structures
- Custom form controls and reusable validators
- Asynchronous operations and third-party integrations (Zod)
- Best practices for testing, accessibility, and styling
@clydin clydin force-pushed the mcp/signal-form-examples branch from 3653ee5 to 190a3a0 Compare September 11, 2025 14:18
[control]="registrationForm.email"
[class.invalid]="registrationForm.email().invalid() && registrationForm.email().touched()"
[attr.aria-invalid]="registrationForm.email().invalid()"
aria-describedby="email-errors"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
aria-describedby="email-errors"
[aria-errormessage]="registrationForm.email().invalid() && registrationForm.email().errors().length ? 'email-errors' : null"


- **`[class.invalid]`:** A class binding that applies a CSS class to an element when a condition is true. Here, it's used to style an input based on its validity.
- **`touched` signal:** A signal on the `FieldState` that becomes `true` when the user has interacted with and then left the form control. This is useful for showing errors only after the user has had a chance to enter a value.
- **`aria-describedby`:** An accessibility attribute that links a form control to the element that describes it, such as an error message. This allows screen readers to announce the error when the user focuses on the invalid input.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **`aria-describedby`:** An accessibility attribute that links a form control to the element that describes it, such as an error message. This allows screen readers to announce the error when the user focuses on the invalid input.
- **`aria-errormessage`:** An accessibility attribute that links a form control to any related error messages. This allows screen readers to announce the error when the user focuses on the invalid input.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-errormessage

aria-describedby should be used as extension to the label / placeholder.
aria-errormessage is specifically for feedback on errors ( can be a space separated list of ids ) related to an object


- **Error Visibility:** Errors are only shown when a field is both `invalid` and `touched` (`@if (field().invalid() && field().touched())`). This provides a better user experience by not showing errors before the user has finished typing.
- **Styling:** The `[class.invalid]` binding adds a red border to the input only when it's invalid and has been touched, providing clear visual feedback.
- **Accessibility:** The `id` of the error message container is linked to the input via `aria-describedby`. This tells screen readers to announce the specific error message when the user focuses on the invalid input.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **Accessibility:** The `id` of the error message container is linked to the input via `aria-describedby`. This tells screen readers to announce the specific error message when the user focuses on the invalid input.
- **Accessibility:** The `id` of the error message container is linked to the input via `aria-errormessage`. This tells screen readers to announce the specific error message when the user focuses on the invalid input.

[control]="registrationForm.name"
[class.invalid]="registrationForm.name().invalid() && registrationForm.name().touched()"
[attr.aria-invalid]="registrationForm.name().invalid()"
aria-describedby="name-errors"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
aria-describedby="name-errors"
[aria-errormessage]="registrationForm.name().invalid() && registrationForm.name().errors().length ? 'name-errors' : null"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aria-describedby should be used as extension to the label / placeholder.
aria-errormessage is specifically for feedback on errors ( can be a space separated list of ids ) related to an object

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be ommitted (null) value if there are no errors, otherwise it refers to an id block that is empty or even unexisting.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need angular/angular#63748

type="text"
[control]="registrationForm.name"
[class.invalid]="registrationForm.name().invalid() && registrationForm.name().touched()"
[attr.aria-invalid]="registrationForm.name().invalid()"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one will be supported once angular/angular#63748 gets in and is released

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[attr.aria-invalid] is working right?
[aria-invalid] was not.

</label>
</div>

<button type="submit">Submit</button>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<button type="submit">Submit</button>
<button>Submit</button>

submit is default

} from '@angular/forms/signals';
import { JsonPipe } from '@angular/common';

export interface PasswordForm {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename this to just Password as this is the model, not the actual Form interface.

Suggested change
export interface PasswordForm {
export interface Password {

This file provides the template for the form, showing how to display errors for both field-level and cross-field validation rules.

```html
<form (submit)="handleSubmit(); $event.preventDefault()">
Copy link

@samvloeberghs samvloeberghs Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the purpose of the example I think it's best to include the novalidate, because without native HTML5 form validation will kick in first.

Suggested change
<form (submit)="handleSubmit(); $event.preventDefault()">
<form (submit)="handleSubmit(); $event.preventDefault()" novalidate>


## Key Concepts

- **`[class.invalid]`:** A class binding that applies a CSS class to an element when a condition is true. Here, it's used to style an input based on its validity.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a class is not really required here. The attribute selector [aria-invalid] can be used for styling it as an error.

}
</div>

<button type="submit" [disabled]="!settingsForm().valid()">Save Settings</button>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is considered a bad pattern to disable buttons. It makes forms less accessible.
Plus, if the only way to submit the form is by clicking the button it's also not good. Entering for example will also submit the form if the submit handler on the form element is implemented.

Suggested change
<button type="submit" [disabled]="!settingsForm().valid()">Save Settings</button>
<button type="submit">Save Settings</button>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: @angular/cli detected: feature PR contains a feature commit target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants