ngx-mat-errors
Version:
NgxMatErrors provides an easy, yet flexible solution for displaying error messages in a MatFormField.
411 lines (394 loc) • 19.7 kB
JavaScript
import { AsyncPipe, NgTemplateOutlet, formatDate } from '@angular/common';
import * as i0 from '@angular/core';
import { inject, Injectable, InjectionToken, TemplateRef, Directive, Input, Component, ViewEncapsulation, ChangeDetectionStrategy, ContentChildren, LOCALE_ID, NgModule } from '@angular/core';
import { isObservable, of, filter, startWith, map, combineLatest, ReplaySubject, switchMap, distinctUntilChanged } from 'rxjs';
import { MAT_FORM_FIELD } from '@angular/material/form-field';
import { ControlContainer, AbstractControl, AbstractControlDirective, StatusChangeEvent, ValueChangeEvent } from '@angular/forms';
import { coerceArray } from '@angular/cdk/coercion';
/**
* This class contains the logic of getting the default control of a MatFormField.
* Extend it to implement a custom getter method.
*/
class NgxMatErrorControl {
constructor() {
this.matFormField = inject(MAT_FORM_FIELD, { optional: true });
}
get() {
return this.matFormField?._control;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrorControl, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrorControl }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrorControl, decorators: [{
type: Injectable
}] });
/**
* Provides the default control accessor of a MatFormField.
*/
function provideDefaultNgxMatErrorControl() {
return {
provide: NgxMatErrorControl,
};
}
/**
* Lightweight injection token. When NgxMatErrorDef is not used, only this token will remain, the directive will be tree-shaken.
*/
const NGX_MAT_ERROR_DEF = new InjectionToken('NGX_MAT_ERROR_DEF');
class NgxMatErrorDef {
constructor() {
/**
* Specify the control to be used for error matching.
* @optional
*/
this.ngxMatErrorDefWithControl = undefined;
this.template = inject(TemplateRef);
this.controlContainer = inject(ControlContainer, {
optional: true,
skipSelf: true,
});
}
get control() {
const input = this.ngxMatErrorDefWithControl;
if (typeof input === 'string') {
return this.controlContainer?.control?.get(input) ?? undefined;
}
if (input instanceof AbstractControl) {
return input;
}
return input?.control ?? undefined;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrorDef, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.0", type: NgxMatErrorDef, isStandalone: true, selector: "[ngxMatErrorDef]", inputs: { ngxMatErrorDefFor: "ngxMatErrorDefFor", ngxMatErrorDefWithControl: "ngxMatErrorDefWithControl" }, providers: [
{
provide: NGX_MAT_ERROR_DEF,
useExisting: NgxMatErrorDef,
},
], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrorDef, decorators: [{
type: Directive,
args: [{
selector: '[ngxMatErrorDef]',
standalone: true,
providers: [
{
provide: NGX_MAT_ERROR_DEF,
useExisting: NgxMatErrorDef,
},
],
}]
}], propDecorators: { ngxMatErrorDefFor: [{
type: Input,
args: [{
required: true,
}]
}], ngxMatErrorDefWithControl: [{
type: Input
}] } });
function coerceToObservable(errorMessages) {
if (isObservable(errorMessages)) {
return errorMessages;
}
return of(errorMessages);
}
function distinctUntilErrorChanged(prev, curr) {
if (prev === curr) {
return true;
}
if (!prev || !curr) {
return false;
}
if (prev.template !== curr.template) {
return false;
}
return prev.$implicit === curr.$implicit;
}
/**
* Finds the error key or custom error for a control.
* @returns INgxMatErrorDef | undefined
*/
function findErrorForControl(control, messages, customErrorMessages) {
const errorKeys = Object.keys(control.errors);
return (customErrorMessages.find((customErrorMessage) => errorKeys.some((error) => {
if (error !== customErrorMessage.ngxMatErrorDefFor) {
return false;
}
return (!customErrorMessage.control || customErrorMessage.control === control);
})) ?? errorKeys.find((key) => key in messages));
}
function getAbstractControls(controls) {
if (!controls) {
return;
}
const _controls = coerceArray(controls)
.map((control) => !control
? undefined
: control instanceof AbstractControlDirective
? control.control
: control instanceof AbstractControl
? control
: control.ngControl?.control)
.filter((control) => control != null);
return _controls.length ? _controls : undefined;
}
function getControlWithError(controls) {
const controlChanges = controls.map((control) => control.events.pipe(filter((event) => event instanceof StatusChangeEvent ||
event instanceof ValueChangeEvent), startWith(null), map(() => control)));
return combineLatest(controlChanges).pipe(map((control) => control.find((control) => !!control.errors)));
}
const NGX_MAT_ERROR_DEFAULT_OPTIONS = new InjectionToken('NGX_MAT_ERROR_DEFAULT_OPTIONS');
class NgxMatErrors {
constructor() {
this.messages$ = coerceToObservable(inject(NGX_MAT_ERROR_DEFAULT_OPTIONS));
this.defaultControl = inject(NgxMatErrorControl, {
host: true,
});
this.controlChangedSubject = new ReplaySubject(1);
}
// ContentChildren is set before ngAfterContentInit which is before ngAfterViewInit.
// Before ngAfterViewInit lifecycle hook we can modify the error$ observable without needing another change detection cycle.
// This elaborates the need of rxjs defer;
set customErrorMessages(queryList) {
const firstControlWithError$ = this.controlChangedSubject.pipe(switchMap((_controls) => {
const controls = getAbstractControls(_controls || this.defaultControl.get());
if (!controls) {
return of(null);
}
return getControlWithError(controls);
})), customErrorMessages$ = queryList.changes.pipe(startWith(queryList));
this.error$ = combineLatest([
firstControlWithError$,
customErrorMessages$,
this.messages$,
]).pipe(map(([controlWithError, customErrorMessages, messages]) => {
if (!controlWithError) {
return;
}
const errors = controlWithError.errors, errorOrErrorDef = findErrorForControl(controlWithError, messages, customErrorMessages.toArray());
if (!errorOrErrorDef) {
return;
}
if (typeof errorOrErrorDef === 'object') {
return {
template: errorOrErrorDef.template,
$implicit: errors[errorOrErrorDef.ngxMatErrorDefFor],
};
}
const message = messages[errorOrErrorDef];
return {
$implicit: typeof message === 'function'
? message(errors[errorOrErrorDef])
: message,
};
}), distinctUntilChanged(distinctUntilErrorChanged));
}
// eslint-disable-next-line @angular-eslint/no-input-rename
/**
* @deprecated will be changed to a signal and it won't be possible to set the property from TS.
* Instead of setting it in a directive, the directive should extend the {@link NgxMatErrorControl } class
* and provide itself as it.
*/
set control(control) {
this.controlChangedSubject.next(control);
}
/** @ignore */
ngOnDestroy() {
this.controlChangedSubject.complete();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrors, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.0", type: NgxMatErrors, isStandalone: true, selector: "ngx-mat-errors, [ngx-mat-errors]", inputs: { control: ["ngx-mat-errors", "control"] }, host: { classAttribute: "ngx-mat-errors" }, providers: [provideDefaultNgxMatErrorControl()], queries: [{ propertyName: "customErrorMessages", predicate: NGX_MAT_ERROR_DEF, descendants: true }], ngImport: i0, template: `<ng-template #defaultTemplate let-error>{{ error }}</ng-template
>@if( error$ | async; as error) {
<ng-template
[ngTemplateOutlet]="error.template ?? defaultTemplate"
[ngTemplateOutletContext]="error"
></ng-template>
}`, isInline: true, dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrors, decorators: [{
type: Component,
args: [{
selector: 'ngx-mat-errors, [ngx-mat-errors]',
template: `<ng-template #defaultTemplate let-error>{{ error }}</ng-template
>@if( error$ | async; as error) {
<ng-template
[ngTemplateOutlet]="error.template ?? defaultTemplate"
[ngTemplateOutletContext]="error"
></ng-template>
}`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [AsyncPipe, NgTemplateOutlet],
host: {
class: 'ngx-mat-errors',
},
providers: [provideDefaultNgxMatErrorControl()],
}]
}], propDecorators: { customErrorMessages: [{
type: ContentChildren,
args: [NGX_MAT_ERROR_DEF, { descendants: true }]
}], control: [{
type: Input,
args: ['ngx-mat-errors']
}] } });
function errorMessagesEnFactory(locale, dateFormat = 'shortDate', timeFormat = 'shortTime') {
return {
min: (error) => `Please enter a value greater than or equal to ${error.min}.`,
max: (error) => `Please enter a value less than or equal to ${error.max}.`,
required: `This field is required.`,
email: `Please enter a valid email address.`,
minlength: (error) => `Please enter at least ${error.requiredLength} characters.`,
maxlength: (error) => `Please enter no more than ${error.requiredLength} characters.`,
matDatepickerMin: (error) => {
const formatted = formatDate(error.min, dateFormat, locale);
return `Please enter a date greater than or equal to ${formatted ?? error.min}.`;
},
matDatepickerMax: (error) => {
const formatted = formatDate(error.max, dateFormat, locale);
return `Please enter a date less than or equal to ${formatted ?? error.max}.`;
},
matDatepickerParse: (error) => `Invalid date format.`,
matStartDateInvalid: (error) => `Start date cannot be after end date.`,
matEndDateInvalid: (error) => `End date cannot be before start date.`,
matDatepickerFilter: 'This date is filtered out.',
matTimepickerParse: (error) => `Invalid time format.`,
matTimepickerMin: (error) => {
const formatted = formatDate(error.min, timeFormat, locale);
return `Please enter a time greater than or equal to ${formatted ?? error.min}.`;
},
matTimepickerMax: (error) => {
const formatted = formatDate(error.max, timeFormat, locale);
return `Please enter a time less than or equal to ${formatted ?? error.max}.`;
},
};
}
const NGX_MAT_ERROR_CONFIG_EN = {
provide: NGX_MAT_ERROR_DEFAULT_OPTIONS,
useFactory: errorMessagesEnFactory,
deps: [LOCALE_ID],
};
function errorMessagesHuFactory(locale, dateFormat = 'shortDate', timeFormat = 'shortTime') {
return {
min: (error) => `Nem lehet kisebb, mint ${error.min}.`,
max: (error) => `Nem lehet nagyobb, mint ${error.max}.`,
required: `Kötelező mező.`,
email: `Nem érvényes e-mail cím.`,
minlength: (error) => `Legalább ${error.requiredLength} karakter hosszú lehet.`,
maxlength: (error) => `Legfeljebb ${error.requiredLength} karakter hosszú lehet.`,
server: (error) => error,
matDatepickerMin: (error) => {
const formatted = formatDate(error.min, dateFormat, locale);
// In Hungarian date ends with '.'
return `Nem lehet korábbi dátum, mint ${formatted ?? error.min}`;
},
matDatepickerMax: (error) => {
const formatted = formatDate(error.max, dateFormat, locale);
// In Hungarian date ends with '.'
return `Nem lehet későbbi dátum, mint ${formatted ?? error.max}`;
},
matDatepickerParse: (error) => `Érvénytelen dátum.`,
matStartDateInvalid: (error) => `A kezdő dátum nem lehet a vég dátum után.`,
matEndDateInvalid: (error) => `A vég dátum nem lehet a kezdő dátum előtt.`,
matDatepickerFilter: 'Ez a dátum nem engedélyezett.',
matTimepickerParse: (error) => `Érvénytelen idő.`,
matTimepickerMin: (error) => {
const formatted = formatDate(error.min, timeFormat, locale);
return `Nem lehet korábbi idő, mint ${formatted ?? error.min}.`;
},
matTimepickerMax: (error) => {
const formatted = formatDate(error.max, timeFormat, locale);
return `Nem lehet későbbi idő, mint ${formatted ?? error.max}.`;
},
};
}
const NGX_MAT_ERROR_CONFIG_HU = {
provide: NGX_MAT_ERROR_DEFAULT_OPTIONS,
useFactory: errorMessagesHuFactory,
deps: [LOCALE_ID],
};
function errorMessagesPtBtFactory(locale, dateFormat = 'shortDate', timeFormat = 'shortTime') {
return {
min: (error) => `Informe um valor igual ou maior a ${error.min}.`,
max: (error) => `Informe um valor igual ou menor a ${error.max}.`,
required: `Campo obrigatório.`,
email: `Informe um endereço de email válido.`,
minlength: (error) => `Informe pelo menos ${error.requiredLength} caracteres.`,
maxlength: (error) => `O campo não pode ter mais que ${error.requiredLength} caracteres.`,
matDatepickerMin: (error) => {
const formatted = formatDate(error.min, dateFormat, locale);
return `Informe uma data maior ou igual a ${formatted ?? error.min}.`;
},
matDatepickerMax: (error) => {
const formatted = formatDate(error.max, dateFormat, locale);
return `Informe uma data menor ou igual a ${formatted ?? error.max}.`;
},
matDatepickerParse: (error) => `Formato de data inválido.`,
matStartDateInvalid: (error) => `A data de início não pode ser posterior à data de término.`,
matEndDateInvalid: (error) => `A data de término não pode ser anterior à data de início.`,
matDatepickerFilter: 'Esta data é filtrada.',
matTimepickerParse: (error) => `Formato de hora inválido.`,
matTimepickerMin: (error) => {
const formatted = formatDate(error.min, timeFormat, locale);
return `Insira um horário maior ou igual a ${formatted ?? error.min}.`;
},
matTimepickerMax: (error) => {
const formatted = formatDate(error.max, timeFormat, locale);
return `Insira um horário menor ou igual a ${formatted ?? error.max}.`;
},
};
}
const NGX_MAT_ERROR_CONFIG_PT_BR = {
provide: NGX_MAT_ERROR_DEFAULT_OPTIONS,
useFactory: errorMessagesPtBtFactory,
deps: [LOCALE_ID],
};
class NgxMatErrorsForDateRangePicker extends NgxMatErrorControl {
/** Returns start and end controls of the date range picker. */
get() {
const { _startInput, _endInput } = this.matFormField
._control;
return [_startInput, _endInput];
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrorsForDateRangePicker, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.0", type: NgxMatErrorsForDateRangePicker, isStandalone: true, selector: "[ngx-mat-errors][forDateRangePicker]", host: { classAttribute: "ngx-mat-errors-for-date-range-picker" }, providers: [
{
provide: NgxMatErrorControl,
useExisting: NgxMatErrorsForDateRangePicker,
},
], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrorsForDateRangePicker, decorators: [{
type: Directive,
args: [{
selector: '[ngx-mat-errors][forDateRangePicker]',
standalone: true,
host: {
class: 'ngx-mat-errors-for-date-range-picker',
},
providers: [
{
provide: NgxMatErrorControl,
useExisting: NgxMatErrorsForDateRangePicker,
},
],
}]
}] });
class NgxMatErrorsModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrorsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrorsModule, imports: [NgxMatErrors, NgxMatErrorDef], exports: [NgxMatErrors, NgxMatErrorDef] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrorsModule }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxMatErrorsModule, decorators: [{
type: NgModule,
args: [{
imports: [NgxMatErrors, NgxMatErrorDef],
exports: [NgxMatErrors, NgxMatErrorDef],
}]
}] });
/*
* Public API Surface of ngx-mat-errors
*/
/**
* Generated bundle index. Do not edit.
*/
export { NGX_MAT_ERROR_CONFIG_EN, NGX_MAT_ERROR_CONFIG_HU, NGX_MAT_ERROR_CONFIG_PT_BR, NGX_MAT_ERROR_DEF, NGX_MAT_ERROR_DEFAULT_OPTIONS, NgxMatErrorDef, NgxMatErrors, NgxMatErrorsForDateRangePicker, NgxMatErrorsModule, errorMessagesEnFactory, errorMessagesHuFactory, errorMessagesPtBtFactory };
//# sourceMappingURL=ngx-mat-errors.mjs.map