Skip to content

Instantly share code, notes, and snippets.

@dougal83
Created July 5, 2024 12:07
Show Gist options
  • Save dougal83/90d0ce1c23c9b2d3c06e17499ab0729a to your computer and use it in GitHub Desktop.
Save dougal83/90d0ce1c23c9b2d3c06e17499ab0729a to your computer and use it in GitHub Desktop.
Directive for <mat-error> to generate default messages and convenience for custom.
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { InjectionToken } from '@angular/core';
const defaultErrors: {
[key: string]: any;
} = {
min: ({ minimum, actual }: any) => `Value must be at least ${minimum}`,
max: ({ maximum, actual }: any) => `Value must be less than ${maximum}`,
required: () => `This field is required`,
requiredtrue: () => `This field is required to be true`,
email: () => `Must be a valid email`,
minlength: ({ requiredLength, actualLength }: any) =>
`Must be at least ${requiredLength} characters long`,
maxlength: ({ requiredLength, actualLength }: any) =>
`Must be less than ${requiredLength} characters long`,
pattern: ({ pattern }: any) => `Value must match pattern`,
};
export const DEFAULT_FORM_ERRORS = new InjectionToken('DEFAULT_FORM_ERRORS', {
providedIn: 'root',
factory: () => defaultErrors,
});
import { AsyncPipe } from '@angular/common';
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
Input,
OnDestroy,
inject,
} from '@angular/core';
import { ValidationErrors } from '@angular/forms';
import { MatFormField } from '@angular/material/form-field';
import { BehaviorSubject, Subscription, filter } from 'rxjs';
import { DEFAULT_FORM_ERRORS } from './default-form-errors';
/**
* Handle <mat-form-field> error messages.
*
* @example
* <mat-error matErrorMessage />
* <mat-error
* matErrorMessage
* [customErrors]="{
* required: 'This could be a custom required error'
* }"
* />
*
*
* @usageNotes
* Add 'matErrorMessage' attribute selector to <mat-error> within <mat-form-field>.
* The customErrors input allows custom errors to be supplied.
*/
@Component({
standalone: true,
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'mat-error[matErrorMessage]',
imports: [AsyncPipe],
template: '{{ message$ | async }}',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MatErrorMessageComponent implements AfterViewInit, OnDestroy {
readonly container = inject(MatFormField);
readonly errors = inject(DEFAULT_FORM_ERRORS);
private subscription = new Subscription() || undefined;
message$ = new BehaviorSubject<string>('');
@Input() customErrors?: ValidationErrors;
ngAfterViewInit(): void {
const matControl = this.container._control;
this.subscription = matControl.stateChanges
.pipe(filter(() => matControl.errorState && !matControl.focused))
.subscribe(() => {
const controlErrors = matControl.ngControl?.errors;
if (controlErrors) {
const firstKey = Object.keys(controlErrors)[0];
const getError = this.errors[firstKey];
const text =
this.customErrors?.[firstKey] || getError(controlErrors[firstKey]);
this.setError(text);
} else {
this.setError('');
}
});
}
setError(text: string) {
this.message$.next(text);
}
ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment