UNPKG

@ngspot/ngx-errors

Version:

<p align="center"> <img width="20%" height="20%" src="https://github.com/DmitryEfimenko/ngspot/blob/main/packages/ngx-errors/package/assets/logo.png?raw=true"> </p>

720 lines (702 loc) 30.3 kB
import * as i0 from '@angular/core'; import { InjectionToken, Injectable, inject, signal, Directive, Self, Optional, input, computed, effect, TemplateRef, ViewContainerRef, ChangeDetectorRef, isDevMode } from '@angular/core'; import { toObservable } from '@angular/core/rxjs-interop'; import { Subject, NEVER, merge, of, auditTime, asapScheduler, share, ReplaySubject, timer, switchMap, filter, take, pipe, Subscription, combineLatest } from 'rxjs'; import { distinctUntilChanged, filter as filter$1, switchMap as switchMap$1, map, tap } from 'rxjs/operators'; import * as i1 from '@angular/forms'; import { ControlContainer, FormGroup, FormArray, FormControl } from '@angular/forms'; /** * Provides a way to add to available options for when to display an error for * an invalid control. Options that come by default are * `'touched'`, `'dirty'`, `'touchedAndDirty'`, `'formIsSubmitted'`. */ const CUSTOM_ERROR_STATE_MATCHERS = new InjectionToken('CUSTOM_ERROR_STATE_MATCHERS'); class ShowOnTouchedErrorStateMatcher { isErrorState(control, form) { return !!(control && control.invalid && (control.touched || (form && form.submitted))); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ShowOnTouchedErrorStateMatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ShowOnTouchedErrorStateMatcher, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ShowOnTouchedErrorStateMatcher, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class ShowOnDirtyErrorStateMatcher { isErrorState(control, form) { return !!(control && control.invalid && (control.dirty || (form && form.submitted))); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ShowOnDirtyErrorStateMatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ShowOnDirtyErrorStateMatcher, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ShowOnDirtyErrorStateMatcher, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class ShowOnTouchedAndDirtyErrorStateMatcher { isErrorState(control, form) { return !!(control && control.invalid && ((control.dirty && control.touched) || (form && form.submitted))); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ShowOnTouchedAndDirtyErrorStateMatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ShowOnTouchedAndDirtyErrorStateMatcher, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ShowOnTouchedAndDirtyErrorStateMatcher, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class ShowOnSubmittedErrorStateMatcher { isErrorState(control, form) { return !!(control && control.invalid && form && form.submitted); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ShowOnSubmittedErrorStateMatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ShowOnSubmittedErrorStateMatcher, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ShowOnSubmittedErrorStateMatcher, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class ErrorStateMatchers { constructor() { this.showOnTouchedErrorStateMatcher = inject(ShowOnTouchedErrorStateMatcher); this.showOnDirtyErrorStateMatcher = inject(ShowOnDirtyErrorStateMatcher); this.showOnTouchedAndDirtyErrorStateMatcher = inject(ShowOnTouchedAndDirtyErrorStateMatcher); this.showOnSubmittedErrorStateMatcher = inject(ShowOnSubmittedErrorStateMatcher); this.customErrorStateMatchers = inject(CUSTOM_ERROR_STATE_MATCHERS, { optional: true, }); this.matchers = { touched: this.showOnTouchedErrorStateMatcher, dirty: this.showOnDirtyErrorStateMatcher, touchedAndDirty: this.showOnTouchedAndDirtyErrorStateMatcher, formIsSubmitted: this.showOnSubmittedErrorStateMatcher, }; if (this.customErrorStateMatchers) { this.matchers = { ...this.matchers, ...this.customErrorStateMatchers }; } } get(showWhen) { return this.matchers[showWhen]; } validKeys() { return Object.keys(this.matchers); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ErrorStateMatchers, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ErrorStateMatchers, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ErrorStateMatchers, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); /** * Patches the method to first execute the provided function and then * the original functionality * @param obj Object with the method of interest * @param methodName Method name to patch * @param fn Function to execute before the original functionality */ function patchObjectMethodWith(obj, methodName, fn) { const originalFn = obj[methodName].bind(obj); function updatedFn(...args) { fn(...args); originalFn(...args); } obj[methodName] = updatedFn; } /** * Extract a touched changed observable from an abstract control * @param control AbstractControl * * @usage * ``` * const formControl = new FormControl(); * const touchedChanged$ = extractTouchedChanges(formControl); * ``` */ function extractTouchedChanges(control) { const methods = { markAsTouched: true, markAsUntouched: false, }; return extractMethodsIntoObservable(control, methods).pipe(distinctUntilChanged()); } /** * Extract a dirty changed observable from an abstract control * @param control AbstractControl * * @usage * ``` * const formControl = new FormControl(); * const dirtyChanged$ = extractDirtyChanges(formControl); * ``` */ function extractDirtyChanges(control) { const methods = { markAsDirty: true, markAsPristine: false, }; return extractMethodsIntoObservable(control, methods).pipe(distinctUntilChanged()); } function extractMethodsIntoObservable(control, methods) { const changes$ = new Subject(); Object.keys(methods).forEach((methodName) => { const emitValue = methods[methodName]; patchObjectMethodWith(control, methodName, () => { changes$.next(emitValue); }); }); return changes$.asObservable(); } class NgxError extends Error { constructor(message) { super(`NgxError: ${message}`); } } class ValueMustBeStringError extends NgxError { constructor() { super('Directive ngxError requires a string value'); } } class NoControlError extends NgxError { constructor() { super('Directive ngxErrors requires either control name or control instance'); } } class ControlInstanceError extends NgxError { constructor() { super('Control must be either a FormGroup, FormControl or FormArray'); } } class ControlNotFoundError extends NgxError { constructor(name) { super(`Control "${name}" could not be found`); } } class ParentFormGroupNotFoundError extends NgxError { constructor(name) { super(`Can't search for control "${name}" because parent FormGroup is not found`); } } class InvalidShowWhenError extends NgxError { constructor(showWhen, keys) { super(`Invalid showWhen value: ${showWhen}. Valid values are: ${keys.join(', ')}`); } } class AllErrorsStateService { constructor() { this.state = signal(new Map()); } registerControl(control, parentForm) { const alreadyRegisteredControl = this.state().get(control); if (alreadyRegisteredControl) { alreadyRegisteredControl.registeredInstancesCount++; return; } const watchedEvents$ = eventsTriggeringVisibilityChange$(control, parentForm); this.state.update((map) => { map.set(control, { control, parentForm, watchedEvents$, registeredInstancesCount: 1, errors: signal({}), }); return new Map(map); }); } unregisterControl(control) { const alreadyRegisteredControl = this.state().get(control); if (!alreadyRegisteredControl) { return; } alreadyRegisteredControl.registeredInstancesCount--; if (alreadyRegisteredControl.registeredInstancesCount === 0) { this.state.update((map) => { map.delete(control); return new Map(map); }); } } getControlState(control) { return this.state().get(control); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: AllErrorsStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: AllErrorsStateService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: AllErrorsStateService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); function getErrorStateMatcher(errorStateMatchers, showWhen) { const errorStateMatcher = errorStateMatchers.get(showWhen); if (!errorStateMatcher) { throw new InvalidShowWhenError(showWhen, errorStateMatchers.validKeys()); } return errorStateMatcher; } function eventsTriggeringVisibilityChange$(control, form) { const ngSubmit$ = form ? form.ngSubmit.asObservable() : NEVER; const $ = merge(control.valueChanges, control.statusChanges, ngSubmit$, extractTouchedChanges(control), extractDirtyChanges(control), asyncBugWorkaround$(control), of(null)).pipe( // using auditTime due to the fact that even though touch event // might fire, the control.touched won't be updated at the time // when ErrorStateMatcher check it auditTime(0, asapScheduler), share({ connector: () => new ReplaySubject(1), resetOnComplete: true, resetOnError: true, resetOnRefCountZero: true, })); return $; } /** * control.statusChanges do not emit when there's async validator * https://github.com/angular/angular/issues/41519 * ugly workaround: */ function asyncBugWorkaround$(control) { let $ = NEVER; if (control.asyncValidator && control.status === 'PENDING') { $ = timer(0, 50).pipe(switchMap(() => of(control.status)), filter((x) => x !== 'PENDING'), take(1)); } return $; } class NgxErrorsFormDirective { constructor(ngForm, formGroupDirective) { this.ngForm = ngForm; this.formGroupDirective = formGroupDirective; } get form() { return this.ngForm ?? this.formGroupDirective; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: NgxErrorsFormDirective, deps: [{ token: i1.NgForm, optional: true, self: true }, { token: i1.FormGroupDirective, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.7", type: NgxErrorsFormDirective, isStandalone: true, selector: "form", exportAs: ["ngxErrorsForm"], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: NgxErrorsFormDirective, decorators: [{ type: Directive, args: [{ // eslint-disable-next-line @angular-eslint/directive-selector selector: 'form', exportAs: 'ngxErrorsForm', standalone: true, }] }], ctorParameters: () => [{ type: i1.NgForm, decorators: [{ type: Self }, { type: Optional }] }, { type: i1.FormGroupDirective, decorators: [{ type: Self }, { type: Optional }] }] }); class NgxErrorsBase { constructor() { this.errorsState = inject(AllErrorsStateService); this.formDirective = inject(NgxErrorsFormDirective, { optional: true, skipSelf: true, }); this.parentControlContainer = inject(ControlContainer, { optional: true, host: true, skipSelf: true, }); this.showWhen = input(); this.controlState = computed(() => { const control = this.resolvedControl(); if (!control) { return undefined; } const controlState = this.errorsState.getControlState(control); return controlState; }); this.registerResolvedControl = effect(() => { const control = this.resolvedControl(); if (!control) { return; } const form = this.formDirective?.form ?? null; this.errorsState.registerControl(control, form); }, { allowSignalWrites: true }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: NgxErrorsBase, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "17.3.7", type: NgxErrorsBase, inputs: { showWhen: { classPropertyName: "showWhen", publicName: "showWhen", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: NgxErrorsBase, decorators: [{ type: Directive }] }); const defaultConfig = { showErrorsWhenInput: 'touched', showMaxErrors: null, }; const ERROR_CONFIGURATION = new InjectionToken('ERROR_CONFIGURATION', { factory: () => { return defaultConfig; }, }); function mergeErrorsConfiguration(config) { return { ...defaultConfig, ...config }; } function provideNgxErrorsConfig(config = defaultConfig) { return { provide: ERROR_CONFIGURATION, useValue: mergeErrorsConfiguration(config), }; } /** * Marks the provided control as well as all of its children as dirty * @param options to be passed into control.markAsDirty() call */ function markDescendantsAsDirty(control, options) { control.markAsDirty(options); if (control instanceof FormGroup || control instanceof FormArray) { const controls = Object.keys(control.controls).map((controlName) => control.get(controlName)); controls.forEach((c) => { c.markAsDirty(options); if (c.controls) { markDescendantsAsDirty(c, options); } }); } } function filterOutNullish() { return pipe(filter$1((x) => x != null)); } /* eslint-disable @angular-eslint/directive-selector */ let errorDirectiveId = 0; /** * Directive to provide a validation error for a specific error name. * Used as a child of ngxErrors directive. * * Example: * ```html * <div [ngxErrors]="control"> * <div ngxError="required">This input is required</div> * </div> * ``` */ class ErrorDirective { constructor() { this.subs = new Subscription(); this.config = inject(ERROR_CONFIGURATION); this.errorStateMatchers = inject(ErrorStateMatchers); this.errorsDirective = inject(NgxErrorsBase); this.templateRef = inject(TemplateRef); this.viewContainerRef = inject(ViewContainerRef); this.cdr = inject(ChangeDetectorRef); this.errorDirectiveId = ++errorDirectiveId; this.errorName = input.required({ alias: 'ngxError' }); this.showWhen = input('', { alias: 'ngxErrorShowWhen' }); this.computedShowWhen = computed(() => { const errorDirectiveShowWhen = this.showWhen(); if (errorDirectiveShowWhen) { return errorDirectiveShowWhen; } const errorsDirectiveShowWhen = this.errorsDirective.showWhen(); if (errorsDirectiveShowWhen) { return errorsDirectiveShowWhen; } if (this.config.showErrorsWhenInput === 'formIsSubmitted' && !this.errorsDirective.parentControlContainer) { return 'touched'; } return this.config.showErrorsWhenInput; }); this.errorStateMatcher = computed(() => { const showWhen = this.computedShowWhen(); return getErrorStateMatcher(this.errorStateMatchers, showWhen); }); this.controlState$ = toObservable(this.errorsDirective.controlState).pipe(filterOutNullish()); /** * Calculates whether the error could be shown based on the result of * ErrorStateMatcher and whether there is an error for this particular errorName * The calculation does not take into account config.showMaxErrors * * In addition, it observable produces a side-effect of updating NgxErrorsStateService * with the information of whether this directive could be shown and a side-effect * of updating err object in case it was mutated */ this.couldBeShown$ = combineLatest([ this.controlState$, toObservable(this.errorName), toObservable(this.errorStateMatcher), ]).pipe(switchMap$1(([controlState, errorName, errorStateMatcher]) => controlState.watchedEvents$.pipe(map(() => ({ controlState, errorName, errorStateMatcher, })))), map(({ controlState, errorName, errorStateMatcher }) => { const isErrorState = errorStateMatcher.isErrorState(controlState.control, controlState.parentForm); const hasError = controlState.control.hasError(errorName); const couldBeShown = isErrorState && hasError; const prevCouldBeShown = controlState.errors()[this.errorDirectiveId]; return { prevCouldBeShown, couldBeShown, errorName, controlState, hasError, }; }), tap(({ controlState, errorName, prevCouldBeShown, couldBeShown, hasError, }) => { if (prevCouldBeShown !== couldBeShown) { controlState.errors.update((errors) => { return { ...errors, [this.errorDirectiveId]: couldBeShown }; }); } const err = controlState.control.getError(errorName); const errorUpdated = hasError && JSON.stringify(this.err) !== JSON.stringify(err); if (errorUpdated) { this.err = err; if (this.view) { this.view.context.$implicit = this.err; this.view.markForCheck(); } } })); this.subscribeToCouldBeShown = this.subs.add(this.couldBeShown$.subscribe()); /** * Determines whether the error is shown to the user based on * the value of couldBeShown and the config.showMaxErrors. * In addition, this reacts to the changes in visibility for all * errors associated with the control */ this.isShown = computed(() => { const controlState = this.errorsDirective.controlState(); if (!controlState) { return false; } const errors = controlState.errors(); const couldBeShown = errors[this.errorDirectiveId]; if (!couldBeShown) { return false; } const { showMaxErrors } = this.config; if (!showMaxErrors) { return true; } // get all errors for this control that are possibly visible, // take directive ids associated with them, sort them // and show only these with index <= to config.showMaxErrors return Object.entries(errors) .reduce((acc, curr) => { const [id, couldBeShown] = curr; if (couldBeShown) { acc.push(Number(id)); } return acc; }, []) .sort() .filter((_, ix) => ix < showMaxErrors) .includes(this.errorDirectiveId); }); this.isShownEffect = effect(() => { const isShown = this.isShown(); const control = this.errorsDirective.resolvedControl(); if (!control) { return; } const prevHidden = this.hidden; this.hidden = !isShown; if (isShown) { this.err = control.getError(this.errorName()); } else { this.err = {}; } if (prevHidden !== this.hidden) { this.toggleVisibility(); } this.cdr.detectChanges(); }); this.hidden = true; // eslint-disable-next-line @typescript-eslint/no-explicit-any this.err = {}; } ngAfterViewInit() { this.validateDirective(); } ngOnDestroy() { this.subs.unsubscribe(); } toggleVisibility() { if (this.hidden) { if (this.view) { this.view.destroy(); this.view = undefined; } } else { if (this.view) { this.view.context.$implicit = this.err; this.view.markForCheck(); } else { this.view = this.viewContainerRef.createEmbeddedView(this.templateRef, { $implicit: this.err, }); } } } validateDirective() { const errorName = this.errorName(); if (typeof errorName !== 'string' || errorName.trim() === '') { throw new ValueMustBeStringError(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ErrorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "17.3.7", type: ErrorDirective, isStandalone: true, selector: "[ngxError]", inputs: { errorName: { classPropertyName: "errorName", publicName: "ngxError", isSignal: true, isRequired: true, transformFunction: null }, showWhen: { classPropertyName: "showWhen", publicName: "ngxErrorShowWhen", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["ngxError"], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ErrorDirective, decorators: [{ type: Directive, args: [{ // eslint-disable-next-line @angular-eslint/directive-selector selector: '[ngxError]', exportAs: 'ngxError', standalone: true, }] }] }); /* eslint-disable @angular-eslint/directive-selector */ /** * Directive to hook into the errors of a given control. * * Example: * * ```ts * \@Component({ * template: ` * <div [ngxErrors]="myControl"> * <div ngxError="required">This input is required</div> * </div> * ` * }) * export class MyComponent { * myControl = new FormControl('', Validators.required) * } * ``` * In case the `ngxErrors` directive is a child of a [formGroup], you can specify * the control by the control name similarly how you'd do it with formControlName: * * ```ts * \@Component({ * template: ` * <form [formGroup]="form"> * <div ngxErrors="firstName"> * <div ngxError="required">This input is required</div> * </div> * </form> * ` * }) * export class MyComponent { * form = this.fb.group({ * firstName: ['', Validators.required] * }); * constructor(private fb: FormBuilder) {} * } * ``` */ class ErrorsDirective extends NgxErrorsBase { constructor() { super(...arguments); this.controlInput = input.required({ alias: 'ngxErrors', }); this.resolvedControl = computed(() => { const controlInput = this.controlInput(); // initialize directive only after control input was set AND after // afterViewInit since parentFormGroupDirective might not be resolved // before that if (!this.afterViewInitComplete()) { return; } if (!controlInput) { throw new NoControlError(); } if (typeof controlInput === 'string') { if (!this.parentControlContainer) { throw new ParentFormGroupNotFoundError(controlInput); } const control = this.parentControlContainer.control?.get(controlInput); if (control == null) { throw new ControlNotFoundError(controlInput); } return control; } if (!this.isAbstractControl(controlInput)) { throw new ControlInstanceError(); } return controlInput; }); this.afterViewInitComplete = signal(false); } ngAfterViewInit() { setTimeout(() => { // Use of the setTimeout to ensure that the controlInput was surely set // in all cases. In particular the edge-case where ngModelGroup // declared via template driven forms results in the control being // set later than ngAfterViewInit life-cycle hook is called this.afterViewInitComplete.set(true); }, 0); } isAbstractControl(control) { return (control instanceof FormControl || control instanceof FormArray || control instanceof FormGroup); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ErrorsDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "17.3.7", type: ErrorsDirective, isStandalone: true, selector: "[ngxErrors]", inputs: { controlInput: { classPropertyName: "controlInput", publicName: "ngxErrors", isSignal: true, isRequired: true, transformFunction: null } }, providers: [{ provide: NgxErrorsBase, useExisting: ErrorsDirective }], exportAs: ["ngxErrors"], usesInheritance: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.7", ngImport: i0, type: ErrorsDirective, decorators: [{ type: Directive, args: [{ selector: '[ngxErrors]', exportAs: 'ngxErrors', standalone: true, providers: [{ provide: NgxErrorsBase, useExisting: ErrorsDirective }], }] }] }); const NGX_ERRORS_DECLARATIONS = [ ErrorsDirective, ErrorDirective, NgxErrorsFormDirective, ]; /** * Makes it easy to trigger validation on the control, that depends on * a value of a different control */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function dependentValidator(opts) { let subscribed = false; return (formControl) => { const form = formControl.root; const { watchControl, condition, validator } = opts; const controlToWatch = watchControl(form); if (!controlToWatch) { if (isDevMode()) { console.warn(`dependentValidator could not find specified watchControl`); } return null; } if (!subscribed) { subscribed = true; controlToWatch.valueChanges.subscribe(() => { formControl.updateValueAndValidity(); }); } if (condition === undefined || condition(controlToWatch.value)) { const validatorFn = validator(controlToWatch.value); return validatorFn(formControl); } return null; }; } /* * Public API Surface of ngx-errors */ /** * Generated bundle index. Do not edit. */ export { AllErrorsStateService, CUSTOM_ERROR_STATE_MATCHERS, ControlInstanceError, ControlNotFoundError, ERROR_CONFIGURATION, ErrorDirective, ErrorStateMatchers, ErrorsDirective, InvalidShowWhenError, NGX_ERRORS_DECLARATIONS, NgxError, NgxErrorsBase, NgxErrorsFormDirective, NoControlError, ParentFormGroupNotFoundError, ShowOnDirtyErrorStateMatcher, ShowOnSubmittedErrorStateMatcher, ShowOnTouchedAndDirtyErrorStateMatcher, ShowOnTouchedErrorStateMatcher, ValueMustBeStringError, dependentValidator, filterOutNullish, getErrorStateMatcher, markDescendantsAsDirty, provideNgxErrorsConfig }; //# sourceMappingURL=ngspot-ngx-errors.mjs.map