UNPKG

ngx-reactive-forms-utils

Version:

Reactive forms in Angular are a great way to manage forms. This library provides utilities that make it easier to work with reactive forms.

244 lines (235 loc) 12.1 kB
import * as i0 from '@angular/core'; import { InjectionToken, Component, Inject, Input, ContentChild } from '@angular/core'; import { NgControl } from '@angular/forms'; import { startWith, debounceTime, map, of, EMPTY, combineLatest } from 'rxjs'; import { AsyncPipe, JsonPipe } from '@angular/common'; function addCustomErrorMessage(name, fn) { defaultCustomErrorMessages[name] = fn; } const defaultCustomErrorMessages = { required: () => `This field is required`, minlength: ({ requiredLength, actualLength }) => `Expected length of ${requiredLength} but got ${actualLength}`, maxlength: ({ requiredLength, actualLength }) => `Expected length of ${requiredLength} but got ${actualLength}`, min: ({ min }) => `You must provide a minimum value of ${min}.`, max: ({ max, actual }) => `You entered a value of ${actual}, but the max you can enter is ${max}.`, email: () => `Your email address is not valid.`, number: () => `This field can only contain numbers.`, confirmStringMatch: ({ field1, field2 }) => { const formattedField1 = field1.replace(/([a-zA-Z])(?=[A-Z])/g, '$1 ').toLowerCase(); const formattedField2 = field2.replace(/([a-zA-Z])(?=[A-Z])/g, '$1 ').toLowerCase(); return `The values of ${formattedField1} and ${formattedField2} don't match.`; }, minAge: ({ minAge, actual }) => `You must be at least ${minAge} years old. You are currently ${actual} years old.`, maxAge: ({ maxAge, actual }) => `You must be no more than ${maxAge} years old. You are currently ${actual} years old.`, }; const FORM_ERRORS = new InjectionToken('FORM_ERRORS', { providedIn: 'root', factory: () => defaultCustomErrorMessages, }); const FORM_ERRORS_DEBOUNCE_TIME = new InjectionToken('FORM_ERRORS_DEBOUNCE_TIME', { providedIn: 'root', factory: () => 0, }); class ControlErrorsDisplayComponent { get rulesBroken() { return this.rules.every((rule) => this.control[rule]); } constructor(_errors, debounceTime) { this._errors = _errors; this.debounceTime = debounceTime; this.containerClasses = ''; this.errorClasses = ''; this.rules = ['touched']; this._errorMessages = this._errors; } ngAfterContentInit() { if (this.control) { this.errorsList$ = this.control.statusChanges?.pipe(startWith(this.control.status), debounceTime(this.debounceTime), map(() => { const errors = this.control.errors; if (errors) { return Object.keys(errors).map((errorKey) => { const getError = this._errorMessages[errorKey]; return getError ? getError(errors[errorKey]) : 'Unknown Error'; }); } return []; })); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: ControlErrorsDisplayComponent, deps: [{ token: FORM_ERRORS }, { token: FORM_ERRORS_DEBOUNCE_TIME }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.3", type: ControlErrorsDisplayComponent, isStandalone: true, selector: "ngx-control-errors-display", inputs: { containerClasses: "containerClasses", errorClasses: "errorClasses", rules: "rules" }, queries: [{ propertyName: "control", first: true, predicate: NgControl, descendants: true, static: true }], ngImport: i0, template: "<div class=\"{{ containerClasses }}\">\n\t<ng-content></ng-content>\n\n\t@if (rulesBroken) {\n\t\t@if (errorsList$ | async; as errorsList) {\n\t\t\t@if (errorsList && errorsList.length) {\n\t\t\t\t@for (error of errorsList; track error) {\n\t\t\t\t\t<p class=\"{{ errorClasses }}\">{{ error }}</p>\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</div>\n", styles: [""], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: ControlErrorsDisplayComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-control-errors-display', standalone: true, imports: [AsyncPipe], template: "<div class=\"{{ containerClasses }}\">\n\t<ng-content></ng-content>\n\n\t@if (rulesBroken) {\n\t\t@if (errorsList$ | async; as errorsList) {\n\t\t\t@if (errorsList && errorsList.length) {\n\t\t\t\t@for (error of errorsList; track error) {\n\t\t\t\t\t<p class=\"{{ errorClasses }}\">{{ error }}</p>\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</div>\n" }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [FORM_ERRORS] }] }, { type: undefined, decorators: [{ type: Inject, args: [FORM_ERRORS_DEBOUNCE_TIME] }] }], propDecorators: { containerClasses: [{ type: Input }], errorClasses: [{ type: Input }], rules: [{ type: Input }], control: [{ type: ContentChild, args: [NgControl, { static: true }] }] } }); function calculateAge(birthdate) { const currentDate = fixTimezoneOffset(new Date()); const birthDate = fixTimezoneOffset(new Date(birthdate)); let age = currentDate.getFullYear() - birthDate.getFullYear(); if (currentDate.getMonth() < birthDate.getMonth() || (currentDate.getMonth() === birthDate.getMonth() && currentDate.getDate() < birthDate.getDate())) { age--; } return age; } function fixTimezoneOffset(date) { return new Date(date.getTime() - date.getTimezoneOffset() * 60000); } class CustomValidators { static phoneNumber(control) { if (!control.value) { return null; } // eslint-disable-next-line no-useless-escape const PHONE_REGEX = /^(1\s?)?((\([2-9][0-9]{2}\))|[2-9][0-9]{2})[\s\-]?[2-9][0-9]{2}[\s\-]?[0-9]{4}$/; return PHONE_REGEX.test(control.value) ? null : { phoneNumber: true }; } static number(control) { // eslint-disable-next-line no-useless-escape const NUMBER_REGEX = /^([0-9])*([\.])?[0-9]+$/; return NUMBER_REGEX.test(control.value) ? null : { number: true }; } static validZipCode(control) { if (!control.value) { return null; } // XXXXX or XXXXX-XXXX const ZIP_REGEX = /(^\d{5}$)|(^\d{5}-\d{4}$)/; return ZIP_REGEX.test(control.value) ? null : { validZipCode: true }; } static confirmStringMatch(field1, field2) { return (form) => { const valueControl = form.get(field1); const confirmValueControl = form.get(field2); if (!valueControl || !confirmValueControl || !valueControl.value || !confirmValueControl.value) { return null; } if (confirmValueControl.errors && !confirmValueControl.errors[field2]) { return null; } if (valueControl.value !== confirmValueControl.value) { confirmValueControl.setErrors({ confirmStringMatch: { field1, field2 } }); return { confirmStringMatch: { field1, field2 } }; } confirmValueControl.setErrors(null); return null; }; } static minAge(minAge) { return (control) => { if (!control.value) { return null; } const birthDate = new Date(control.value); const age = calculateAge(birthDate); if (age < minAge) { return { minAge: { minAge, actual: age } }; } return null; }; } static maxAge(maxAge) { return (control) => { if (!control.value) { return null; } const birthDate = new Date(control.value); const age = calculateAge(birthDate); if (age > maxAge) { return { maxAge: { maxAge, actual: age } }; } return null; }; } } var FormDebugFieldEnum; (function (FormDebugFieldEnum) { FormDebugFieldEnum["Value"] = "Value"; FormDebugFieldEnum["FormErrors"] = "FormErrors"; FormDebugFieldEnum["ControlErrors"] = "ControlErrors"; FormDebugFieldEnum["Status"] = "Status"; FormDebugFieldEnum["Valid"] = "Valid"; FormDebugFieldEnum["Invalid"] = "Invalid"; })(FormDebugFieldEnum || (FormDebugFieldEnum = {})); const DEFAULT_DEBUG_FIELDS = Object.keys(FormDebugFieldEnum).map((key) => key); function debugForm(form, debugFields = [...DEFAULT_DEBUG_FIELDS]) { if (!form) { return of(EMPTY); } return combineLatest([ form.valueChanges.pipe(startWith(form.value)), form.statusChanges.pipe(startWith(form.status)), ]).pipe(map(() => { const returnObject = {}; if (debugFields.includes(FormDebugFieldEnum.Value)) { returnObject.value = form.value; } if (debugFields.includes(FormDebugFieldEnum.FormErrors)) { returnObject.formErrors = form.errors; } if (debugFields.includes(FormDebugFieldEnum.ControlErrors)) { const controlErrors = Object.keys(form.controls).reduce((acc, controlName) => { const control = form.get(controlName); return { ...acc, [controlName]: { errors: control?.errors, status: control?.status, }, }; }, {}); returnObject.controlErrors = controlErrors; } if (debugFields.includes(FormDebugFieldEnum.Status)) { returnObject.status = form.status; } if (debugFields.includes(FormDebugFieldEnum.Valid)) { returnObject.valid = form.valid; } return returnObject; })); } class FormDebugDisplayComponent { constructor() { this.debugFields = [...DEFAULT_DEBUG_FIELDS]; } ngOnChanges(changes) { if (changes['form'] || changes['debugFields']) { if (this.form) { this.debugData$ = debugForm(this.form, this.debugFields); } } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: FormDebugDisplayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.3", type: FormDebugDisplayComponent, isStandalone: true, selector: "ngx-form-debug-display", inputs: { debugFields: "debugFields", form: "form" }, usesOnChanges: true, ngImport: i0, template: "@if (form) {\n\t<pre><code>\n{{ debugData$ | async | json }}\n</code></pre>\n}\n", styles: ["pre{padding:0 12px 12px;background-color:#f4f4f4;border:1px solid #333;border-radius:5px}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: JsonPipe, name: "json" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: FormDebugDisplayComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-form-debug-display', standalone: true, imports: [AsyncPipe, JsonPipe], template: "@if (form) {\n\t<pre><code>\n{{ debugData$ | async | json }}\n</code></pre>\n}\n", styles: ["pre{padding:0 12px 12px;background-color:#f4f4f4;border:1px solid #333;border-radius:5px}\n"] }] }], propDecorators: { debugFields: [{ type: Input }], form: [{ type: Input, args: [{ required: true }] }] } }); /** * Generated bundle index. Do not edit. */ export { ControlErrorsDisplayComponent, CustomValidators, DEFAULT_DEBUG_FIELDS, FORM_ERRORS, FORM_ERRORS_DEBOUNCE_TIME, FormDebugDisplayComponent, FormDebugFieldEnum, addCustomErrorMessage, debugForm, defaultCustomErrorMessages }; //# sourceMappingURL=ngx-reactive-forms-utils.mjs.map