'Angular Material custom form field doesn't show mat-error
I thought I have almost the same example but somehow the control tricks me :-/
<form [formGroup]="form">
<app-ref-urlcheck [maxLen]="20" formControlName="url"></app-ref-urlcheck>
</form>
and the template looks like
<mat-form-field>
<input matInput #inUrl="ngModel" [(ngModel)]="value" type="url" [attr.maxlength]="maxLen" [errorStateMatcher]="errorStateMatcher"
(input)="changeInput(inUrl.value)" [disabled]="isDisabled" [value]="strUrl"
placeholder="Homepage" />
<mat-error>test error</mat-error> <!-- doesn't show up - neither the next -->
<mat-error *ngIf="(inUrl.touched && inUrl.invalid)">This field is required</mat-error>
</mat-form-field>
and the main content
import { Component, HostListener, Input, OnInit } from '@angular/core';
import { AbstractControl, ControlValueAccessor, FormControl, NgControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';
import { MatFormFieldControl } from '@angular/material/form-field';
import { Observable } from 'rxjs';
@Component({
selector: 'app-ref-urlcheck',
templateUrl: './ref-urlcheck.component.html',
styleUrls: ['./ref-urlcheck.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: RefURLcheckComponent
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: RefURLcheckComponent
}
]
})
export class RefURLcheckComponent implements OnInit, ControlValueAccessor, MatFormFieldControl<any>, Validator {
@Input() maxLen = 254;
strUrl: string;
onChange = (changedUrl) => { };
onTouched = () => { };
isDisabled = false;
touched = false;
@HostListener('focusin', ['$event.target.value']) onFocusIn;
constructor() { }
onContainerClick(event: MouseEvent): void {
throw new Error('Method not implemented.');
}
setDescribedByIds(ids: string[]): void {
throw new Error('Method not implemented.');
}
userAriaDescribedBy?: string;
autofilled?: boolean;
controlType?: string;
errorState: boolean;
disabled: boolean;
required: boolean;
shouldLabelFloat: boolean;
empty: boolean;
focused: boolean;
ngControl: NgControl;
placeholder: string;
id: string;
stateChanges: Observable<void>;
value: any;
ngOnInit(): void {
}
setDisabledState?(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}
registerOnTouched(onTouched: () => {}): void {
this.onTouched = onTouched;
}
registerOnChange(onChange: (changedValue: string) => {}): void {
this.onChange = onChange;
this.onFocusIn = (inputVal) => {
console.log('focus in', inputVal);
this.markAsTouched();
};
}
writeValue(value: string): void {
this.strUrl = value;
}
markAsTouched() {
if (!this.touched) {
this.onTouched();
this.touched = true;
}
}
changeInput(inVal: string) {
this.onChange(inVal);
this.markAsTouched();
}
readonly errorStateMatcher: ErrorStateMatcher = {
isErrorState: (ctrl: FormControl) => {
console.log('errorStateMatch...')
this.errorState = true;
return (ctrl && ctrl.invalid);
}
};
validate(control: AbstractControl): ValidationErrors | null {
if (control?.value.length <= 5) {
this.errorState = true;
return {
tooShort: true
};
}
this.errorState = false;
return null;
}
}
Same question as in the referred example: How to display <mat-error>
? It doesn't even show up anyhow.
Solution 1:[1]
Reused the attached code and suspect that the FormControl
didn't update with an error when the validation is failed.
When the validation fails, should set the error to FormControl
as below:
this.inUrl.control.setErrors({ tooShort: true });
import { ViewChild } from '@angular/core';
export class RefURLcheckComponent
implements OnInit, ControlValueAccessor, MatFormFieldControl<any>, Validator
{
@ViewChild('inUrl', { static: true }) inUrl: NgControl;
...
validate(control: AbstractControl): ValidationErrors | null {
if (control?.value?.length <= 5) {
this.errorState = true;
this.inUrl.control.setErrors({ tooShort: true });
return {
tooShort: true,
};
}
this.errorState = false;
this.inUrl.control.setErrors(null);
return null;
}
}
Solution 2:[2]
Thanx to @Yong Shun
I figured out how to do the management properly. It seems like that one needs to wrap an input
field with the regular template driven approach (for the updates of the input
field) and use this component as reactive one. So my custom control has inside a control which handles the states.
I removed from my original code all the unnecessary stuff and included some minor hints from the guideline.
So here is my (streamlined) working example - for the sake when somebody needs it again.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Yong Shun |
Solution 2 | LeO |