Reputation: 17128
I have a common component as below, where I am using mat-error
@Component({
selector: 'falcon-validation-error',
standalone: true,
imports: [CommonModule, MatInputModule, ],
template: `
<mat-error>kkk</mat-error>
`,
})
export class ValidationErrorComponent {}
In the HTML of another component
<mat-form-field>
<mat-label>Enter your email</mat-label>
<input matInput
placeholder="[email protected]"
[formControl]="email"
required>
@if (email.invalid) {
<falcon-validation-error/>
}
</mat-form-field>
email = new FormControl('', [Validators.required, Validators.email]);
The design looks ugly
However, if I directly use the mat-error
<mat-form-field>
<mat-label>Enter your email</mat-label>
<input matInput
placeholder="[email protected]"
[formControl]="email"
required>
@if (email.invalid) {
<mat-error>kkk</mat-error>
}
</mat-form-field>
Update
I have created a common component for error display. For example, in the textbox component, I am calling the falcon-error component to display the error
@Component({
selector: 'falcon-textbox',
standalone: true,
imports: [MatInputModule, ...sharedControlDeps],
viewProviders: [controlProvider],
template: `
<mat-form-field appearance="outline" class="w-full">
<mat-label>{{ control.config.label }}</mat-label>
<input matInput [formControlName]="control.formControlName" [placeholder]="control.config.placeHolder"
[container]="containerDir.container">
</mat-form-field>
<falcon-error [error]=“errors”/>
`,
styles: `.w-full {
width: 100%
}`,
})
export class TextboxComponent extends BaseControlBuilder {
}
@Component({
selector: 'falcon-error',
standalone: true,
imports: [CommonModule, MatInputModule, ErrorMessagePipe],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@if (errors) {
@for (error of errors | keyvalue; ) {
<mat-error>{{ error.key | errorMessage:error.value }}</mat-error>
}
}
`,
})
export class ValidationErrorComponent {
@Input() errors: ValidationErrors | undefined | null = null;
}
Upvotes: 0
Views: 438
Reputation: 58019
It's a bit complex and change with the changes of material angular.
In this SO show how join two errors using an unique mat-error and inside a list of errors.
Hardly based in this link, we can make a component. The problem it's that material-angular changes how show the errors, so I put the code only for the last version of material. In this version the only we need is change the height of the div with class "mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align".
The most important part is get the FormField and the control. For this we inject in constructor the @Host()
of matformField
constructor(@Host() private formField: MatFormField) {}
And in ngAfterViewInit
ngAfterViewInit() {
this.control =
this.formField?._formFieldControl?.ngControl?.control || null;
...
}
With the new Angular we can subscribe to control.events to know when we need change the height
ngAfterViewInit() {
this.control =
this.formField?._formFieldControl?.ngControl?.control || null;
if (this.control)
this.subscription = this.control.events.subscribe((res: any) => {
const wrapper =
this.formField._elementRef.nativeElement.getElementsByClassName(
'mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align'
);
const numError =
this.control?.touched && this.control?.errors
? Object.keys(this.control?.errors).length
: 0;
const hints = this.formField._hintChildren.length;
const height = !numError ? '' : 8 + numError * 16 + 'px';
if (wrapper.length) {
(wrapper[0] as HTMLElement).style.height = height;
}
});
}
Now just define the template and a bit of the .css
@Component({
selector: '[mat-error-custom]',
standalone: true,
template:`
<ul class="ul-clear">
@for(error of getError();track $index){
<li>{{error.error}}</li>
}
</ul>
`,
styles:[`
ul.ul-clear{
display: block;
list-style-type: disc;
margin-block-start: -16px;
margin-block-end: 0;
margin-inline-start: 0px;
margin-inline-end: 0px;
padding-inline-start: 0;
unicode-bidi: isolate;
}
`]
})
And define the function getErrors()
getError() {
let error: any[] = [];
if (this.control?.errors) {
Object.keys(this.control.errors).forEach((firstKey) => {
switch (firstKey) {
case 'required':
error.push({ error: 'required' });
break;
case 'minlength':
error.push({
error:
' is required ' +
this.control?.errors?.minlength.requiredLength +
' characteres',
});
break;
case 'pattern':
error.push({ error: " don't match with pattern" });
break;
case 'maxlength':
error.push({
error:
' is required less than ' +
this.control?.errors?.maxlength.requiredLength +
' characteres',
});
break;
default:
error.push({error:this.control?.errors?.errorText})
}
});
}
return error.length ? error : null;
}
the stackblitz
Upvotes: 0
Reputation: 58019
I feel that the best bet is "extending" the mat-error directive. Well if you create a directive with the same selector of mat-error, each time you use mat-error your "mat-error" is a "super" mat-error and you can access to the mat-error and the elementRef
@Directive({
selector: 'mat-error, [matError]',
standalone: true,
})
export class CustomError implements OnInit {
constructor(private elementRef: ElementRef,private matError:MatError)
{
console.log(this.matError,this.elementRef)
}
}
ngOnInit()
{
I can do anything
}
You can use also any selector and write
<mat-error super-mat-error>this is an error</mat-error>
But really I don't know what are you trying to do
Upvotes: 0