-
Notifications
You must be signed in to change notification settings - Fork 27.1k
Description
I'm submitting a...
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
Current behavior
This is an example of how validation errors could be displayed for a password input field:
<form [formGroup]="signupForm" #formDir="ngForm">
<label for="password">Password*</label>
<input required type="password" name="password" formControlName="password" [ngClass]="{
'myCSSinvalid myCSSinvalid--inline': !signupForm.get('password').valid &&
(signupForm.get('password').touched || signupForm.get('password').dirty || formDir.submitted),
'myCSSvalid myCSSvalid--inline': signupForm.get('password').valid &&
(signupForm.get('password').touched || signupForm.get('password').dirty || formDir.submitted)}">
<div class="message" *ngIf="signupForm.get('password').errors['required'] &&
(signupForm.get('password').touched || signupForm.get('password').dirty || formDir.submitted)">
Password is required
</div>
<div class="message" *ngIf="!signupForm.get('password').valid &&
!signupForm.get('password').errors['required'] &&
(signupForm.get('password').touched || signupForm.get('password').dirty || formDir.submitted)">
Password has to be at least 8 characters long
</div>
</form>
The code
(signupForm.get('password').touched || signupForm.get('password').dirty || formDir.submitted)
is repetitive and requires to allways specify name of the control. Also in general there is too much logic in the template.
Expected behavior
I think angular/material2 team recognized the complexity too and they built the ErrorStateMatcher class which simplifies the logic. Here is an example of how it can be used:
<form class="example-form">
<mat-form-field class="example-full-width">
<input matInput placeholder="Email" [formControl]="emailFormControl"
[errorStateMatcher]="matcher">
<mat-hint>Errors appear instantly!</mat-hint>
<mat-error *ngIf="emailFormControl.hasError('email') && !emailFormControl.hasError('required')">
Please enter a valid email address
</mat-error>
<mat-error *ngIf="emailFormControl.hasError('required')">
Email is <strong>required</strong>
</mat-error>
</mat-form-field>
</form>
typescript:
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
}
}
/** @title Input with a custom ErrorStateMatcher */
@Component({
selector: 'input-error-state-matcher-example',
templateUrl: './input-error-state-matcher-example.html',
styleUrls: ['./input-error-state-matcher-example.css'],
})
export class InputErrorStateMatcherExample {
emailFormControl = new FormControl('', [
Validators.required,
Validators.email,
]);
matcher = new MyErrorStateMatcher();
}
With [errorStateMatcher]="matcher" they provide a custom matcher to the input component which receives a reference to the FormControl in question and to the whole Form (FormGroup) as well. Then a part of the logic is offloaded from template to a custom ErrorStateMatcher. I think this makes the template easier to read. The logic is in one place and one can define a few ErrorStateMatchers which cover all behaviors. The UI designers can read the template code better.
One could take it even one step further and instead of emailFormControl.hasError('email') have something like thisControl.hasError('email') which would automatically inject the closest control in the XML/HTML tree into thisControl.
Full code for this example:
https://github.com/angular/material2/tree/9b4f4355bc89cb83295f10b8d1bea6ba3ab4d6d5/src/material-examples/input-error-state-matcher
ErrorStateMatcher is implemented here: https://github.com/angular/material2/blob/0834a31d03ae6707633e01d43200703840b70c5a/src/lib/core/error/error-options.ts
What is the motivation / use case for changing the behavior?
I think this makes the template easier to read. The logic is in one place and one can define a few ErrorStateMatchers which cover all behaviors. The UI designers can read the template code better.
Environment
Angular version: 5.0.1
Browser:
- [x ] Chrome (desktop) version XX
- [x ] Chrome (Android) version XX
- [x ] Chrome (iOS) version XX
- [x ] Firefox version XX
- [x ] Safari (desktop) version XX
- [x ] Safari (iOS) version XX
- [x ] IE version XX
- [x ] Edge version XX
For Tooling issues:
- Node version: XX
- Platform:
Others: