UNPKG

ngx-mat-errors

Version:

NgxMatErrors provides an easy, yet flexible solution for displaying error messages in a MatFormField.

405 lines (388 loc) 20 kB
import { NgTemplateOutlet, formatDate } from '@angular/common'; import * as i0 from '@angular/core'; import { inject, signal, afterNextRender, Injectable, InjectionToken, TemplateRef, Input, Directive, isSignal, contentChildren, input, computed, ChangeDetectionStrategy, ViewEncapsulation, Component, LOCALE_ID, NgModule } from '@angular/core'; import { toSignal, rxResource } from '@angular/core/rxjs-interop'; import { MAT_FORM_FIELD } from '@angular/material/form-field'; import { ControlContainer, AbstractControl, AbstractControlDirective, StatusChangeEvent, ValueChangeEvent } from '@angular/forms'; import { coerceArray } from '@angular/cdk/coercion'; import { filter, startWith, map, combineLatest, defaultIfEmpty, isObservable } from 'rxjs'; /** * This class contains the logic of getting the default control of a MatFormField. * Extend it to implement a custom getter method. */ class NgxMatErrorControl { get() { return this.matFormField?._control; } constructor() { this.matFormField = inject(MAT_FORM_FIELD, { optional: true }); this.control = signal(undefined, ...(ngDevMode ? [{ debugName: "control" }] : [{}])); afterNextRender(() => { this.control.set(this.get()); }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NgxMatErrorControl, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NgxMatErrorControl }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NgxMatErrorControl, decorators: [{ type: Injectable }], ctorParameters: () => [] }); /** * 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: "20.3.17", ngImport: i0, type: NgxMatErrorDef, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.17", 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: "20.3.17", 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 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 []; } return coerceArray(controls) .map((control) => !control ? undefined : control instanceof AbstractControlDirective ? control.control : control instanceof AbstractControl ? control : control.ngControl?.control) .filter((control) => control != null); } 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)), defaultIfEmpty(undefined)); } function getMessagesAsSignal(errorMessages) { if (isObservable(errorMessages)) { return toSignal(errorMessages, { requireSync: true }); } if (isSignal(errorMessages)) { return errorMessages; } return signal(errorMessages); } const NGX_MAT_ERROR_DEFAULT_OPTIONS = new InjectionToken('NGX_MAT_ERROR_DEFAULT_OPTIONS'); class NgxMatErrors { constructor() { this.messages = getMessagesAsSignal(inject(NGX_MAT_ERROR_DEFAULT_OPTIONS)); this.customErrorMessages = contentChildren(NGX_MAT_ERROR_DEF, ...(ngDevMode ? [{ debugName: "customErrorMessages", descendants: true }] : [{ descendants: true, }])); this.defaultControl = inject(NgxMatErrorControl, { host: true, }); this.control = input(undefined, ...(ngDevMode ? [{ debugName: "control", alias: 'ngx-mat-errors' }] : [{ alias: 'ngx-mat-errors', }])); this.controlWithError = rxResource({ params: () => ({ control: this.control(), defaultControl: this.defaultControl.control(), }), stream: ({ params }) => getControlWithError(getAbstractControls(params.control || params.defaultControl)), // The latest control with error is always returned, even if it has the same error as the previous one, because the error object itself might have changed (e.g. new validation errors were set on the control). equal: (a, b) => a === b && !a, }); this.error = computed(() => { const controlWithError = this.controlWithError.value(), customErrorMessages = this.customErrorMessages(), messages = this.messages(); if (!controlWithError) { return; } const errors = controlWithError.errors, errorOrErrorDef = findErrorForControl(controlWithError, messages, customErrorMessages); 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, }; }, ...(ngDevMode ? [{ debugName: "error", equal: distinctUntilErrorChanged }] : [{ equal: distinctUntilErrorChanged, }])); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NgxMatErrors, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: NgxMatErrors, isStandalone: true, selector: "ngx-mat-errors, [ngx-mat-errors]", inputs: { control: { classPropertyName: "control", publicName: "ngx-mat-errors", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "ngx-mat-errors" }, providers: [provideDefaultNgxMatErrorControl()], queries: [{ propertyName: "customErrorMessages", predicate: NGX_MAT_ERROR_DEF, descendants: true, isSignal: true }], ngImport: i0, template: `<ng-template #defaultTemplate let-error>{{ error }}</ng-template> @if (error(); as error) { <ng-template [ngTemplateOutlet]="error.template ?? defaultTemplate" [ngTemplateOutletContext]="error" ></ng-template> }`, isInline: true, dependencies: [{ 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: "20.3.17", 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(); as error) { <ng-template [ngTemplateOutlet]="error.template ?? defaultTemplate" [ngTemplateOutletContext]="error" ></ng-template> }`, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], host: { class: 'ngx-mat-errors', }, providers: [provideDefaultNgxMatErrorControl()], }] }], propDecorators: { customErrorMessages: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NGX_MAT_ERROR_DEF), { ...{ descendants: true, }, isSignal: true }] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngx-mat-errors", required: false }] }] } }); 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() { if (!this.matFormField?._control) { return; } const { _startInput, _endInput } = this.matFormField ._control; return [_startInput, _endInput]; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NgxMatErrorsForDateRangePicker, deps: null, target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.17", 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: "20.3.17", 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: "20.3.17", ngImport: i0, type: NgxMatErrorsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.17", ngImport: i0, type: NgxMatErrorsModule, imports: [NgxMatErrors, NgxMatErrorDef], exports: [NgxMatErrors, NgxMatErrorDef] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NgxMatErrorsModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", 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