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 MatError
s 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