UNPKG

ngx-custom-form-error

Version:

This library makes showing errors in angular forms easier.

237 lines (228 loc) 15.1 kB
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