ngx-custom-form-error
Version:
This library makes showing errors in angular forms easier.
237 lines (228 loc) • 15.1 kB
JavaScript
import * as i0 from '@angular/core';
import { Directive, InjectionToken, ElementRef, Component, ChangeDetectionStrategy, Inject, Optional, ContentChild, Input, NgModule } from '@angular/core';
import { FormControlName } from '@angular/forms';
import { Subject, Observable, combineLatest, of } from 'rxjs';
import { takeUntil, distinctUntilChanged, switchMap, tap, map } from 'rxjs/operators';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
class CustomFormControlLabelDirective {
constructor(el) {
this.el = el;
}
}
CustomFormControlLabelDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.7", ngImport: i0, type: CustomFormControlLabelDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
CustomFormControlLabelDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.2.7", type: CustomFormControlLabelDirective, selector: "[cLabel]", ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.7", ngImport: i0, type: CustomFormControlLabelDirective, decorators: [{
type: Directive,
args: [{
selector: "[cLabel]"
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }]; } });
function hasTwoObjectsSameProps(x, y) {
let xProp, yProp;
try {
xProp = Object.getOwnPropertyNames(x);
}
catch {
xProp = [];
}
try {
yProp = Object.getOwnPropertyNames(y);
}
catch {
yProp = [];
}
if (xProp.length !== yProp.length)
return false;
return xProp.every((prop) => yProp.includes(prop));
}
const CUSTOM_FORM_CONFIG = new InjectionToken('Custom-Form-Config');
class NgxCustomFormErrorComponent {
constructor(config) {
this.config = config;
this._destroy$ = new Subject();
this.defaultConfig = {
errorClass: 'c-control-error',
errorTextColor: '#ee3e3e',
addErrorClassToElement: true,
onTouchedOnly: false
};
}
ngAfterContentInit() {
this.init();
this.errors$ = this.getErrors();
}
init() {
this.onTouchedOnly = this.onTouchedOnly ?? this.config?.onTouchedOnly ?? this.defaultConfig.onTouchedOnly;
this.addErrorClassToElement = this.addErrorClassToElement ?? this.config?.addErrorClassToElement ?? this.defaultConfig.addErrorClassToElement;
this.errorTextColor = this.errorTextColor ?? this.config?.errorTextColor ?? this.defaultConfig.errorTextColor;
this.errorClass = this.config?.errorClass ?? this.defaultConfig.errorClass;
this.label = this.label ?? this.labelRef?.el.nativeElement.innerText ?? undefined;
this.initmessages();
}
/*** We compose messages object from user inputs and global config here, which will use to show the error */
initmessages() {
// We are checking for undefined because input property can be null if user don't want to show error that is configured
// in the global config.
this.messages = {
required: this.required !== undefined ? this.required : this.config?.required,
min: this.min !== undefined ? this.min : this.config?.min,
max: this.max !== undefined ? this.max : this.config?.max,
minlength: this.minlength !== undefined ? this.minlength : this.config?.minLength,
maxlength: this.maxlength !== undefined ? this.maxlength : this.config?.maxLength,
email: this.email !== undefined ? this.email : this.config?.email,
pattern: this.pattern !== undefined ? this.pattern : this.config?.pattern
};
}
getErrors() {
const touched$ = new Observable((subscribe => {
// If onTouchedOnly is `true` the observable emits `true` only after the element is touched
if (this.onTouchedOnly) {
// Thsee are use to trigger if the input is marked as touched
this.formControl.valueAccessor?.registerOnTouched(() => subscribe.next(true));
this.formControl.valueChanges?.pipe(takeUntil(this._destroy$)).subscribe(() => this.formControl.touched ? subscribe.next(true) : null);
}
else {
// If onTouchedOnly is `false` the observable emits `true` so as to start looking for error rightaway
subscribe.next(true);
}
})).pipe(distinctUntilChanged());
return combineLatest([touched$, this.formControl.statusChanges]).pipe(switchMap(() => of(this.formControl.errors)), tap(errors => {
if (!this.addErrorClassToElement)
return;
// if config has addErrorClassToElement set to true, we set the errorClass to the element thas has `formControlName` directive.
if (errors) {
this.controlElement.nativeElement.classList.add(this.errorClass);
}
else {
this.controlElement.nativeElement.classList.remove(this.errorClass);
}
}),
// This observable will emit everytime we input on the element. And a simple distinctUntilChanged will not work
// since it will emit either `null` or `error object` with same properties. But two objects cannot be same
// so I have used `hasTwoObjectsSameProps` to stop emitting if the error object has same properties (meaning it is same ) as previous one.
distinctUntilChanged((x, y) => hasTwoObjectsSameProps(x, y)), map((errorObj) => {
if (!errorObj)
return [];
let errors = Object.keys(errorObj).map(key => {
let errorKey = key;
if (!this.messages[errorKey])
return;
if (typeof this.messages[errorKey] == 'string') {
return this.messages[errorKey];
}
else {
let errorFn = this.messages[errorKey];
return errorFn(this.label, errorObj[errorKey]);
}
});
// This to eliminate array of undefined and nulls
return errors.filter(error => error);
}));
}
ngOnDestroy() {
this._destroy$.next();
}
}
NgxCustomFormErrorComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.7", ngImport: i0, type: NgxCustomFormErrorComponent, deps: [{ token: CUSTOM_FORM_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Component });
NgxCustomFormErrorComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.7", type: NgxCustomFormErrorComponent, selector: "c-form-error", inputs: { required: "required", maxlength: ["maxLength", "maxlength"], minlength: ["minLength", "minlength"], min: "min", max: "max", email: "email", pattern: "pattern", onTouchedOnly: "onTouchedOnly", addErrorClassToElement: "addErrorClassToElement", errorTextColor: "errorTextColor", maxLengthCount: "maxLengthCount", label: "label" }, queries: [{ propertyName: "formControl", first: true, predicate: FormControlName, descendants: true }, { propertyName: "controlElement", first: true, predicate: FormControlName, descendants: true, read: ElementRef }, { propertyName: "labelRef", first: true, predicate: CustomFormControlLabelDirective, descendants: true }], ngImport: i0, template: "<ng-content></ng-content>\r\n<div class=\"c-additions\">\r\n <div class=\"c-error-container\" *ngIf=\"(errors$ | async) as errors;else emptyDiv\">\r\n <ng-container *ngIf=\"errors.length\">\r\n <ng-container *ngFor=\"let error of errors\">\r\n <div [style.color]=\"errorTextColor\" class=\"c-error\">\r\n {{error}}\r\n </div>\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n <div *ngIf=\"formControl && maxLengthCount\" class=\"c-counter\">\r\n <span class=\"c-counter-left-bracket\">[</span>\r\n <span class=\"c-counter-first-elem\">\r\n {{(formControl.valueChanges | async)?.length || formControl.value?.length || 0}}\r\n </span>\r\n <span class=\"c-counter-divider\">\r\n /\r\n </span>\r\n <span class=\"c-counter-last-elem\">\r\n {{maxLengthCount}}\r\n </span>\r\n <span class=\"c-counter-right-bracket\">]</span>\r\n\r\n </div>\r\n</div>\r\n<ng-template #emptyDiv>\r\n <div></div>\r\n</ng-template>", styles: [".c-additions{display:flex;gap:1rem;justify-content:space-between}.c-additions .c-error-container{display:flex;flex-direction:column;gap:.5rem;padding:0 3px}.c-additions .c-counter{min-width:-moz-fit-content;min-width:fit-content;width:-moz-fit-content;width:fit-content;text-align:end;margin-top:.3rem}.c-additions .c-error:first-child{margin-top:.3rem}\n"], directives: [{ type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], pipes: { "async": i1.AsyncPipe }, changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.7", ngImport: i0, type: NgxCustomFormErrorComponent, decorators: [{
type: Component,
args: [{ selector: 'c-form-error', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>\r\n<div class=\"c-additions\">\r\n <div class=\"c-error-container\" *ngIf=\"(errors$ | async) as errors;else emptyDiv\">\r\n <ng-container *ngIf=\"errors.length\">\r\n <ng-container *ngFor=\"let error of errors\">\r\n <div [style.color]=\"errorTextColor\" class=\"c-error\">\r\n {{error}}\r\n </div>\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n <div *ngIf=\"formControl && maxLengthCount\" class=\"c-counter\">\r\n <span class=\"c-counter-left-bracket\">[</span>\r\n <span class=\"c-counter-first-elem\">\r\n {{(formControl.valueChanges | async)?.length || formControl.value?.length || 0}}\r\n </span>\r\n <span class=\"c-counter-divider\">\r\n /\r\n </span>\r\n <span class=\"c-counter-last-elem\">\r\n {{maxLengthCount}}\r\n </span>\r\n <span class=\"c-counter-right-bracket\">]</span>\r\n\r\n </div>\r\n</div>\r\n<ng-template #emptyDiv>\r\n <div></div>\r\n</ng-template>", styles: [".c-additions{display:flex;gap:1rem;justify-content:space-between}.c-additions .c-error-container{display:flex;flex-direction:column;gap:.5rem;padding:0 3px}.c-additions .c-counter{min-width:-moz-fit-content;min-width:fit-content;width:-moz-fit-content;width:fit-content;text-align:end;margin-top:.3rem}.c-additions .c-error:first-child{margin-top:.3rem}\n"] }]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [CUSTOM_FORM_CONFIG]
}, {
type: Optional
}] }]; }, propDecorators: { formControl: [{
type: ContentChild,
args: [FormControlName]
}], controlElement: [{
type: ContentChild,
args: [FormControlName, { read: ElementRef }]
}], labelRef: [{
type: ContentChild,
args: [CustomFormControlLabelDirective]
}], required: [{
type: Input
}], maxlength: [{
type: Input,
args: ['maxLength']
}], minlength: [{
type: Input,
args: ['minLength']
}], min: [{
type: Input
}], max: [{
type: Input
}], email: [{
type: Input
}], pattern: [{
type: Input
}], onTouchedOnly: [{
type: Input
}], addErrorClassToElement: [{
type: Input
}], errorTextColor: [{
type: Input
}], maxLengthCount: [{
type: Input
}], label: [{
type: Input
}] } });
class NgxCustomFormErrorModule {
static rootConfig(config) {
if (NgxCustomFormErrorModule.config)
throw new Error("NgxCustomFormErrorModule.rootConfig() method cannot be called more than once in an application. Use NgxCustomFormErrorModule.childConfig() method if you want to pass extra configuration.");
NgxCustomFormErrorModule.config = config;
return {
ngModule: NgxCustomFormErrorModule,
providers: [{
provide: CUSTOM_FORM_CONFIG,
useValue: config
}
]
};
}
static childConfig(config) {
return {
ngModule: NgxCustomFormErrorModule,
providers: [{
provide: CUSTOM_FORM_CONFIG,
useValue: { ...NgxCustomFormErrorModule.config, ...config }
}
]
};
}
}
NgxCustomFormErrorModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.7", ngImport: i0, type: NgxCustomFormErrorModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
NgxCustomFormErrorModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.2.7", ngImport: i0, type: NgxCustomFormErrorModule, declarations: [NgxCustomFormErrorComponent,
CustomFormControlLabelDirective], imports: [CommonModule], exports: [NgxCustomFormErrorComponent,
CustomFormControlLabelDirective] });
NgxCustomFormErrorModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.2.7", ngImport: i0, type: NgxCustomFormErrorModule, imports: [[
CommonModule,
]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.7", ngImport: i0, type: NgxCustomFormErrorModule, decorators: [{
type: NgModule,
args: [{
declarations: [
NgxCustomFormErrorComponent,
CustomFormControlLabelDirective
],
imports: [
CommonModule,
],
exports: [
NgxCustomFormErrorComponent,
CustomFormControlLabelDirective
]
}]
}] });
/*
* Public API Surface of ngx-custom-form-error
*/
/**
* Generated bundle index. Do not edit.
*/
export { CustomFormControlLabelDirective, NgxCustomFormErrorComponent, NgxCustomFormErrorModule };
//# sourceMappingURL=ngx-custom-form-error.mjs.map