Skip to content

Commit d323766

Browse files
committed
docs(material/input): add example for showing errors from a parent form group
Errors from a validator on a parent form group, such as a check that two fields match, are set on the group instead of the input, so `mat-error` doesn't show them by default. Add an example that uses an `ErrorStateMatcher` to show a parent group's error on the related input. Fixes #8513
1 parent 0585595 commit d323766

5 files changed

Lines changed: 95 additions & 0 deletions

File tree

‎src/components-examples/material/input/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ export {InputErrorsSignalFormExample} from './input-errors-signal-form/input-err
55
export {InputFormExample} from './input-form/input-form-example';
66
export {InputHintExample} from './input-hint/input-hint-example';
77
export {InputOverviewExample} from './input-overview/input-overview-example';
8+
export {InputParentFormErrorsExample} from './input-parent-form-errors/input-parent-form-errors-example';
89
export {InputPrefixSuffixExample} from './input-prefix-suffix/input-prefix-suffix-example';
910
export {InputHarnessExample} from './input-harness/input-harness-example';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.example-form {
2+
min-width: 150px;
3+
max-width: 500px;
4+
width: 100%;
5+
}
6+
7+
.example-full-width {
8+
width: 100%;
9+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<form class="example-form" [formGroup]="passwordForm">
2+
<mat-form-field class="example-full-width">
3+
<mat-label>Password</mat-label>
4+
<input type="password" matInput formControlName="password">
5+
@if (passwordForm.controls.password.hasError('required')) {
6+
<mat-error>Password is <strong>required</strong></mat-error>
7+
}
8+
</mat-form-field>
9+
10+
<mat-form-field class="example-full-width">
11+
<mat-label>Confirm password</mat-label>
12+
<input type="password" matInput formControlName="confirmPassword" [errorStateMatcher]="matcher">
13+
@if (passwordForm.controls.confirmPassword.hasError('required')) {
14+
<mat-error>Please confirm your password</mat-error>
15+
} @else if (passwordForm.hasError('passwordsMismatch')) {
16+
<mat-error>Passwords don't match</mat-error>
17+
}
18+
</mat-form-field>
19+
</form>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {Component} from '@angular/core';
2+
import {
3+
AbstractControl,
4+
FormControl,
5+
FormGroup,
6+
FormGroupDirective,
7+
NgForm,
8+
ReactiveFormsModule,
9+
ValidationErrors,
10+
ValidatorFn,
11+
Validators,
12+
} from '@angular/forms';
13+
import {ErrorStateMatcher} from '@angular/material/core';
14+
import {MatFormFieldModule} from '@angular/material/form-field';
15+
import {MatInputModule} from '@angular/material/input';
16+
17+
/** Validates that the `password` and `confirmPassword` controls of a form group match. */
18+
const passwordsMatchValidator: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
19+
const password = group.get('password')?.value;
20+
const confirmPassword = group.get('confirmPassword')?.value;
21+
return password === confirmPassword ? null : {passwordsMismatch: true};
22+
};
23+
24+
/**
25+
* Error state matcher that also shows the parent form group's `passwordsMismatch` error
26+
* once the user has interacted with the control or the form has been submitted.
27+
*/
28+
export class PasswordsMismatchErrorStateMatcher implements ErrorStateMatcher {
29+
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
30+
if (!control) {
31+
return false;
32+
}
33+
34+
const hasInteracted = control.dirty || control.touched || !!form?.submitted;
35+
const hasError = control.invalid || !!control.parent?.hasError('passwordsMismatch');
36+
return hasInteracted && hasError;
37+
}
38+
}
39+
40+
/**
41+
* @title Input with errors from a parent form group
42+
*/
43+
@Component({
44+
selector: 'input-parent-form-errors-example',
45+
templateUrl: 'input-parent-form-errors-example.html',
46+
styleUrl: 'input-parent-form-errors-example.css',
47+
imports: [MatFormFieldModule, MatInputModule, ReactiveFormsModule],
48+
})
49+
export class InputParentFormErrorsExample {
50+
protected passwordForm = new FormGroup(
51+
{
52+
password: new FormControl('', Validators.required),
53+
confirmPassword: new FormControl('', Validators.required),
54+
},
55+
{validators: passwordsMatchValidator},
56+
);
57+
58+
protected matcher = new PasswordsMismatchErrorStateMatcher();
59+
}

‎src/material/input/input.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ should be shown. (`true` indicating that they should be shown, and `false` indic
7373

7474
<!-- example(input-error-state-matcher) -->
7575

76+
Errors from a validator on a parent form group, such as a check that two fields match, are set on
77+
the group rather than on the input. Because the input's own control stays valid, these errors are
78+
not shown by default. To show them, use an `ErrorStateMatcher` that also checks the parent group's
79+
errors.
80+
81+
<!-- example(input-parent-form-errors) -->
82+
7683
A global error state matcher can be specified by setting the `ErrorStateMatcher` provider. This
7784
applies to all inputs. For convenience, `ShowOnDirtyErrorStateMatcher` is available in order to
7885
globally cause input errors to show when the input is dirty and invalid.

0 commit comments

Comments
 (0)