-
Notifications
You must be signed in to change notification settings - Fork 11.9k
feat(@angular/cli): add initial set of signal form examples #31134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
93f4632
to
0c84e42
Compare
dd1d83d
to
3653ee5
Compare
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
3653ee5
to
190a3a0
Compare
[control]="registrationForm.email" | ||
[class.invalid]="registrationForm.email().invalid() && registrationForm.email().touched()" | ||
[attr.aria-invalid]="registrationForm.email().invalid()" | ||
aria-describedby="email-errors" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- **`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. |
There was a problem hiding this comment.
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
|
||
- **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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- **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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aria-describedby="name-errors" | |
[aria-errormessage]="registrationForm.name().invalid() && registrationForm.name().errors().length ? 'name-errors' : null" |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()" |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<button type="submit">Submit</button> | |
<button>Submit</button> |
submit is default
} from '@angular/forms/signals'; | ||
import { JsonPipe } from '@angular/common'; | ||
|
||
export interface PasswordForm { |
There was a problem hiding this comment.
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.
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()"> |
There was a problem hiding this comment.
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.
<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. |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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.
<button type="submit" [disabled]="!settingsForm().valid()">Save Settings</button> | |
<button type="submit">Save Settings</button> |
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 theng mcp
command.The examples cover the following key concepts: