UNPKG

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