Dynamically create a <mat-error> component and have it be projected into the parent <mat-form-field> component properly

yotube
0

Issue

Using the ComponentFactoryResolver to dynamically create a component is straight forward, but it doesn't seem to project the created component into the parent when I try and do this for the Angular Material <mat-error> component within a <mat-form-field>. I've been using Netanel Basal's example with Bootstrap components, which works great for generating form control errors, but due to the content projection used in Angular Material with <mat-error> I can't seem to get it to work for Angular Material v8.2.1.

Is it possible to dynamically create a <mat-error> in a <mat-form-field> and have it content project properly?


Solution

I've tried this before - MatFormField picks up MatErrors via ContentChildren. Since it's content projection, dynamically adding or removing elements like MatError doesn't work -- the content template is compiled and handled differently than other templates.

You mentioned in a comment that you're trying to handle consistent error messaging across templates...why not add a directive to the <mat-error> element that controls the error messaging based on the parent control's form validation state?

If you're looking to control templates, though, you can't use a directive. You can, however, create a component and use it like a directive:

Create new file mat-error-messages.component.ts, this is full code of file:

import {AfterViewInit, Component, Injector} from '@angular/core';
import {MatFormField, MatFormFieldControl} from '@angular/material/form-field';
import {MatInput} from '@angular/material/input';

@Component({
selector: '[matErrorMessages]',
template: '{{ error }}'
})
export class MatErrorMessagesComponent implements AfterViewInit {

public error = '';
private inputRef: MatFormFieldControl<MatInput>;

constructor(private _inj: Injector) {
}

public ngAfterViewInit(): void {
// grab reference to MatFormField directive, where form control is accessible.
const container = this._inj.get(MatFormField);
this.inputRef = container._control;

// sub to the control's status stream
this.inputRef.ngControl.statusChanges.subscribe(this.updateErrors);
}


private updateErrors = (state: 'VALID' | 'INVALID'): void => {
if (state === 'INVALID') {
// active errors on the FormControl
const controlErrors = this.inputRef.ngControl.errors;

// just grab one error
const firstError = Object.keys(controlErrors)[0];

if (firstError === 'required')
{this.error = 'This field is required.';}

if (firstError === 'minlength')
{this.error = 'This field should be longer.';}

if (firstError === 'error from my own custom validator')
{this.error = 'You get the point.';}
// .....
}
};
}

then in template....

<mat-error matErrorMessages></mat-error>

This way, you let the MatFormField control the presence of the MatError as it's supposed to do, but you control the content of the error element in a localized, clean way.

stackblitz of the above: https://stackblitz.com/edit/angular-sjkfft

You have to dig in to the Material components' private members, which is questionable practice, and can break with library updates, but it works.

I actually have a repo for this type of error messaging that was written for a much older version of @angular/material, but I was also able to get a hold of the Validator itself for better error messaging, and was injecting a whole list of custom validators/errors for that validator into the module: https://github.com/joh04667/material-error-messages



Answered By - joh04667

Post a Comment

0Comments
Post a Comment (0)

#buttons=(Accept !) #days=(20)

Our website uses cookies to enhance your experience. Learn More
Accept !
To Top