ngx-valdemort
Version:
Simple, consistent validation error messages for your Angular forms
687 lines (675 loc) • 42.2 kB
JavaScript
import * as i0 from '@angular/core';
import { signal, Injectable, inject, TemplateRef, input, Directive, contentChildren, contentChild, computed, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
import { ControlContainer } from '@angular/forms';
import { NgTemplateOutlet } from '@angular/common';
/**
* Service used by the default validation errors directive to store the default error template references. This
* service is injected in the validation errors component which displays the appropriate templates and provides their context.
*/
class DefaultValidationErrors {
directives = signal([], { ...(ngDevMode ? { debugName: "directives" } : {}) });
fallback = signal(undefined, { ...(ngDevMode ? { debugName: "fallback" } : {}) });
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: DefaultValidationErrors, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: DefaultValidationErrors, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: DefaultValidationErrors, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/**
* Directive allowing to define the template for an error of a given type (using the `valError` input), using an ng-template.
* It's used inside the body of the validation errors component, or inside the body of the default validation errors directive.
* See the documentation of these two for example usages.
*/
class ValidationErrorDirective {
templateRef = inject(TemplateRef);
/**
* The type of the error that the content of the template must display.
*/
type = input.required({ ...(ngDevMode ? { debugName: "type" } : {}), alias: 'valError' });
static ngTemplateContextGuard(_directive, context) {
return true;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValidationErrorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.2", type: ValidationErrorDirective, isStandalone: true, selector: "ng-template[valError]", inputs: { type: { classPropertyName: "type", publicName: "valError", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValidationErrorDirective, decorators: [{
type: Directive,
args: [{
selector: 'ng-template[valError]'
}]
}], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "valError", required: true }] }] } });
/**
* Directive allowing to define a fallback template for an error of a type that is not handled by any validation error directive.
* It's used inside the body of the validation errors component, or inside the body of the default validation errors directive.
* See the documentation of these two for example usages.
*
* This is useful to handle forgotten errors instead of displaying no error at all, or to handle all or several error types in the same way,
* for example by relying on the error key to choose an internationalized message.
*/
class ValidationFallbackDirective {
templateRef = inject(TemplateRef);
static ngTemplateContextGuard(_directive, context) {
return true;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValidationFallbackDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: ValidationFallbackDirective, isStandalone: true, selector: "ng-template[valFallback]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValidationFallbackDirective, decorators: [{
type: Directive,
args: [{
selector: 'ng-template[valFallback]'
}]
}] });
/**
* Directive allowing to register default templates for validation error messages. It's supposed to be used once,
* typically in the root component. By using templates to do that, error messages can
* - easily be i18ned
* - easily use pipes
* - easily use HTML
* - easily be ordered
*
* Example usage:
* ```
* <val-default-errors>
* <ng-template valError="required">This field is mandatory</ng-template>
* <ng-template valError="max" let-error="error">This field must be at most {{ error.max | number }}</ng-template>
* </val-default-errors>
* ```
*
* Example usage where a label is used to make the messages less generic:
* ```
* <val-default-errors>
* <ng-template valError="required" let-label>{{ label }} is mandatory</ng-template>
* <ng-template valError="max" let-error="error" let-label>{{ label }} must be at most {{ error.max | number }}</ng-template>
* </val-default-errors>
* ```
*
* A fallback template can also be provided. This fallback template is used for all the errors that exist on the form control
* but are not handled by any of the specific error templates:
* ```
* <val-default-errors>
* <ng-template valError="required" let-label>{{ label }} is mandatory</ng-template>
* <ng-template valError="max" let-error="error" let-label>{{ label }} must be at most {{ error.max | number }}</ng-template>
* <ng-template valFallback let-label let-type="type" let-error="error">{{ label }} has an unhandled error of type {{ type }}: {{ error | json }}</ng-template>
* </val-default-errors>
* ```
* Using the fallback can also be used to handle all the errors the same way, for example by using the error type as an i18n key
* to display the appropriate error message.
*
* This directive stores the default template references in a service, that is then injected in the validation errors components
* to be reused.
*/
class DefaultValidationErrorsDirective {
defaultValidationErrors = inject(DefaultValidationErrors);
/**
* The list of validation error directives (i.e. <ng-template valError="...">)
* contained inside the directive element.
*/
errorDirectives = contentChildren(ValidationErrorDirective, { ...(ngDevMode ? { debugName: "errorDirectives" } : {}) });
/**
* The validation fallback directive (i.e. <ng-template valFallback>) contained inside the directive element.
*/
fallbackDirective = contentChild(ValidationFallbackDirective, { ...(ngDevMode ? { debugName: "fallbackDirective" } : {}) });
ngAfterContentInit() {
this.defaultValidationErrors.directives.set(this.errorDirectives());
this.defaultValidationErrors.fallback.set(this.fallbackDirective());
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: DefaultValidationErrorsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.0.2", type: DefaultValidationErrorsDirective, isStandalone: true, selector: "val-default-errors", queries: [{ propertyName: "errorDirectives", predicate: ValidationErrorDirective, isSignal: true }, { propertyName: "fallbackDirective", first: true, predicate: ValidationFallbackDirective, descendants: true, isSignal: true }], ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: DefaultValidationErrorsDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: 'val-default-errors'
}]
}], propDecorators: { errorDirectives: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ValidationErrorDirective), { isSignal: true }] }], fallbackDirective: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ValidationFallbackDirective), { isSignal: true }] }] } });
/**
* The display mode of the validation errors. For a given control, either all the validation errors
* are displayed, or only the first one.
*/
var DisplayMode;
(function (DisplayMode) {
DisplayMode[DisplayMode["ALL"] = 0] = "ALL";
DisplayMode[DisplayMode["ONE"] = 1] = "ONE";
})(DisplayMode || (DisplayMode = {}));
/**
* The configuration service used by the validation errors component to apply common rules for all
* form controls.
*
* To change its default behavior, you can either inject this service in your root module or component and mutate it,
* or define your own implementation and provide it.
*/
class ValdemortConfig {
/**
* The display mode of the errors. The default value is ALL, meaning that all the errors existing on a control
* (and which have an error template defined) are displayed.
*/
displayMode = DisplayMode.ALL;
/**
* Specifies one or several CSS classes (separated by a white space) that are automatically added to the
* validation errors element. This can be useful to reuse a standard CSS class of your CSS framework (like
* .invalid-feedback in BootStrap), rather than styling the val-errors element itself.
*
* The default value is null (no class is added).
*/
errorsClasses = null;
/**
* Specifies one or several CSS classes (separated by a white space) that are automatically added to the
* each validation error message element. This can be useful to reuse a standard CSS class of your CSS framework
* rather than styling the div element itself.
*
* The default value is null (no class is added).
*/
errorClasses = null;
/**
* Specifies when error messages should be displayed based on the state of the control itself (touched, dirty, etc.)
* and on the state of the form directive containing it (if any). This function is only called if the control is invalid
* in the first place: if it's valid, errors are never displayed.
*
* The default value of this function returns true if the control is touched, or if the form (if any) is submitted.
*/
shouldDisplayErrors = (control, form) => control.touched || (!!form && form.submitted);
/**
* **Experimental**
*
* Specifies when error messages should be displayed based on the state of the field (touched, dirty, etc.).
* This function must be reactive (i.e. it must return its value by reading signals).
* Note that if the field is valid, errors are never displayed, whatever ths function returns.
*
* The default value of this function returns true if the field is touched.
*/
shouldDisplayFieldErrors = (fieldState) => fieldState.touched();
/**
* Specifies if the library should throw an error when a control is not found.
* For example, this can happen if a typo was made in the `controlName`.
* If the check is enabled, then an error will be thrown in such a case.
* Otherwise, the error is silently ignored.
*
* The default value of this function returns false, thus disabling the check.
*
* You can enable the check by giving it a function that returns true,
* or you can enable it only in development for example with:
* `config.shouldThrowOnMissingControl = () => !environment.production`
*/
shouldThrowOnMissingControl = () => false;
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValdemortConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValdemortConfig, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValdemortConfig, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
const NO_ERRORS$1 = {
shouldDisplayErrors: false
};
const NO_VALIDATION_STATE = {
control: null,
errorsDisplayed: null,
errors: null
};
function areValidationStatesEqual(previous, current) {
return previous.control === current.control && previous.errorsDisplayed === current.errorsDisplayed && previous.errors === current.errors;
}
/**
* Component allowing to display validation error messages associated to a given form control, form group or form array.
* The control is provided using the `control` input of the component. If it's used inside an enclosing form group or
* form array, it can instead be provided using the `controlName` input of the component.
*
* Example usage where the control itself is being passed as input:
* ```
* <val-errors [control]="form.controls.birthDate">
* <ng-template valError="required">The birth date is mandatory</ng-template>
* <ng-template valError="max" let-error="error">The max value for the birth date is {{ error.max | number }}</ng-template>
* </val-errors>
* ```
*
* Example usage where the control name is being passed as input:
* ```
* <val-errors controlName="birthDate">
* <ng-template valError="required">The birth date is mandatory</ng-template>
* <ng-template valError="max" let-error="error">The max value for the birth date is {{ error.max | number }}</ng-template>
* </val-errors>
* ```
*
* This component, if the control is invalid, displays its validation errors using the provided templates.
* The templates, as shown in the above example, have access to the validation error itself.
*
* The label of the control can also be provided as input, and then used in the templates:
* ```
* <val-errors controlName="birthDate" label="the birth date">
* <ng-template valError="required" let-label>{{ label }} is mandatory</ng-template>
* <ng-template valError="max" let-error="error" let-label>The max value for {{ label }} is {{ error.max | number }}</ng-template>
* </val-errors>
* ```
*
* The component‘s behavior is configured globally by the Config service (see its documentation for more details). It can
* - display the first error, or all the errors
* - add CSS classes to its host `<val-errors>` element
* - add CSS classes to each error message element being displayed
* - choose when to display the errors (dirty, touched, touched and submitted, etc.)
*
* Global, default templates can be defined (and used by this component) using the default validation errors directive
* (see its documentation for details). So, if the default error messages are defined and sufficient for a given control, all you
* need is
*
* ```
* <val-errors controlName="birthDate"></val-errors>
* ```
*
* or, if the default templates expect a label:
*
* ```
* <val-errors controlName="birthDate" label="the birth date"></val-errors>
* ```
*
* If, however, you want to override one or several error messages by custom ones, you can do so by simply defining them inside the
* component:
*
* ```
* <val-errors controlName="birthDate" label="the birth date">
* <ng-template valError="max">You're too young, sorry</ng-template>
* </val-errors>
* ```
*
* A fallback template can also be provided. This fallback template is used for all the errors that exist on the form control
* but are not handled by any of the specific error templates:
* ```
* <val-errors controlName="birthDate" label="the birth date">
* <ng-template valError="max">You're too young, sorry</ng-template>
* <ng-template valFallback let-label let-type="type" let-error="error">{{ label }} has an unhandled error of type {{ type }}: {{ error | json }}</ng-template>
* </val-errors>
* ```
* Note that, the fallback template can also be defined in the default validation errors directive (see its documentation for details).
* If a fallback template is defined inside `val-errors`, it overrides the default fallback.
*
* If an error is present on the control, but doesn't have any template, default template or fallback template defined for its type,
* then it's not displayed. If the control is valid, or if none of the errors of the component has a matching template or default template,
* then this component itself is hidden.
*/
class ValidationErrorsComponent {
/**
* The FormControl, FormGroup or FormArray containing the validation errors.
* If set, the controlName input is ignored
*/
control = input(null, { ...(ngDevMode ? { debugName: "control" } : {}) });
/**
* The name (or the index, in case it's contained in a FormArray) of the FormControl, FormGroup or FormArray containing the validation
* errors.
* Ignored if the control input is set, and only usable if the control to validate is part of a control container
*/
controlName = input(null, { ...(ngDevMode ? { debugName: "controlName" } : {}) });
/**
* The label of the field, exposed to templates so they can use it in the error message.
*/
label = input(null, { ...(ngDevMode ? { debugName: "label" } : {}) });
/**
* The list of validation error directives (i.e. <ng-template valError="...">) contained inside the component element.
*/
errorDirectives = contentChildren(ValidationErrorDirective, { ...(ngDevMode ? { debugName: "errorDirectives" } : {}) });
/**
* The validation fallback directive (i.e. <ng-template valFallback>) contained inside the component element.
*/
fallbackDirective = contentChild(ValidationFallbackDirective, { ...(ngDevMode ? { debugName: "fallbackDirective" } : {}) });
/**
* The Config service instance, defining the behavior of this component
*/
config = inject(ValdemortConfig);
errorsClasses = this.config.errorsClasses || '';
errorClasses = this.config.errorClasses || '';
validationState = signal(NO_VALIDATION_STATE, { ...(ngDevMode ? { debugName: "validationState" } : {}), equal: areValidationStatesEqual });
/**
* The DefaultValidationErrors service instance, holding the default error templates,
* optionally defined by using the default validation errors directive
*/
defaultValidationErrors = inject(DefaultValidationErrors);
/**
* The control container, if it exists, as one of the 4 form group or form array directives that can "wrap" the control.
* It's injected so that we can know if it exists and, if it does, if its form directive has been submitted or not:
* the config service shouldDisplayErrors function can choose (and does by default) to use that information.
*/
controlContainer = inject(ControlContainer, { optional: true });
vm = computed(() => {
const ctrl = this.validationState().control;
if (this.shouldDisplayErrors(ctrl)) {
const errorsToDisplay = this.findErrorsToDisplay(ctrl);
return {
shouldDisplayErrors: true,
control: ctrl,
errorsToDisplay
};
}
else {
return NO_ERRORS$1;
}
}, { ...(ngDevMode ? { debugName: "vm" } : {}) });
ngDoCheck() {
const ctrl = this.findActualControl();
if (ctrl) {
const formDirective = this.controlContainer?.formDirective;
const errorsDisplayed = this.config.shouldDisplayErrors(ctrl, formDirective);
this.validationState.set({
control: ctrl,
errorsDisplayed,
errors: ctrl.errors
});
}
else {
this.validationState.set(NO_VALIDATION_STATE);
}
}
shouldDisplayErrors(ctrl) {
if (!ctrl || !ctrl.invalid || !this.hasDisplayableError(ctrl)) {
return false;
}
const form = this.controlContainer && this.controlContainer.formDirective;
return this.config.shouldDisplayErrors(ctrl, form ?? undefined);
}
findErrorsToDisplay(ctrl) {
const mergedDirectives = [];
const fallbackErrors = [];
const alreadyMetTypes = new Set();
const shouldContinue = () => this.config.displayMode === DisplayMode.ALL || (mergedDirectives.length === 0 && fallbackErrors.length === 0);
const defaultValidationErrorDirectives = this.defaultValidationErrors.directives();
for (let i = 0; i < defaultValidationErrorDirectives.length && shouldContinue(); i++) {
const defaultDirective = defaultValidationErrorDirectives[i];
if (ctrl.hasError(defaultDirective.type())) {
const customDirectiveOfSameType = this.errorDirectives().find(dir => dir.type() === defaultDirective.type());
mergedDirectives.push(customDirectiveOfSameType || defaultDirective);
}
alreadyMetTypes.add(defaultDirective.type());
}
if (shouldContinue()) {
const customDirectives = this.errorDirectives();
for (let i = 0; i < customDirectives.length && shouldContinue(); i++) {
const customDirective = customDirectives[i];
if (!alreadyMetTypes.has(customDirective.type()) && ctrl.hasError(customDirective.type())) {
mergedDirectives.push(customDirective);
}
alreadyMetTypes.add(customDirective.type());
}
}
if (shouldContinue() && (this.fallbackDirective() || this.defaultValidationErrors.fallback())) {
const allErrors = Object.entries(ctrl.errors ?? []);
for (let i = 0; i < allErrors.length && shouldContinue(); i++) {
const [type, value] = allErrors[i];
if (!alreadyMetTypes.has(type)) {
fallbackErrors.push({ type, value });
}
}
}
return {
errors: mergedDirectives,
fallback: this.fallbackDirective() ?? this.defaultValidationErrors.fallback(),
fallbackErrors
};
}
findActualControl() {
const ctrl = this.control();
const ctrlName = this.controlName();
if (ctrl) {
return ctrl;
}
else if (ctrlName != null && this.controlContainer?.control?.controls) {
// whether the control is a FormGroup or a FormArray, we must use .control[ctrlName] to get it
const control = (this.controlContainer?.control).controls[ctrlName];
if (this.config.shouldThrowOnMissingControl()) {
// if the control is null, then there are two cases:
// - we are in a template driven form, and the controls might not be initialized yet
// - there was an error in the control name. If so, let's throw an error to help developers
// to avoid false positive in template driven forms, we check if the controls are initialized
// by checking if the `controls` object or array has any element
if (!control && Object.keys(this.controlContainer?.control?.controls).length > 0) {
throw new Error(`ngx-valdemort: no control found for controlName: '${ctrlName}'.`);
}
}
return control;
}
return null;
}
hasDisplayableError(ctrl) {
return (ctrl.errors &&
(this.fallbackDirective() ||
this.defaultValidationErrors.fallback() ||
Object.keys(ctrl.errors).some(type => this.defaultValidationErrors.directives().some(dir => dir.type() === type) ||
this.errorDirectives().some(dir => dir.type() === type))));
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValidationErrorsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.2", type: ValidationErrorsComponent, isStandalone: true, selector: "val-errors", inputs: { control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "errorsClasses", "style.display": "vm().shouldDisplayErrors ? '' : 'none'" } }, queries: [{ propertyName: "errorDirectives", predicate: ValidationErrorDirective, isSignal: true }, { propertyName: "fallbackDirective", first: true, predicate: ValidationFallbackDirective, descendants: true, isSignal: true }], ngImport: i0, template: "@let vm = this.vm();\n@if (vm.shouldDisplayErrors) {\n @for (errorDirective of vm.errorsToDisplay.errors; track $index) {\n <div [class]=\"errorClasses\">\n <ng-container\n *ngTemplateOutlet=\"\n errorDirective!.templateRef;\n context: {\n $implicit: label(),\n error: vm.control.errors![errorDirective.type()]\n }\n \"\n />\n </div>\n }\n @for (error of vm.errorsToDisplay.fallbackErrors; track $index) {\n <div [class]=\"errorClasses\">\n <ng-container\n *ngTemplateOutlet=\"\n vm.errorsToDisplay.fallback!.templateRef;\n context: {\n $implicit: label(),\n type: error.type,\n error: error.value\n }\n \"\n />\n </div>\n }\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValidationErrorsComponent, decorators: [{
type: Component,
args: [{ selector: 'val-errors', host: {
'[class]': 'errorsClasses',
'[style.display]': `vm().shouldDisplayErrors ? '' : 'none'`
}, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let vm = this.vm();\n@if (vm.shouldDisplayErrors) {\n @for (errorDirective of vm.errorsToDisplay.errors; track $index) {\n <div [class]=\"errorClasses\">\n <ng-container\n *ngTemplateOutlet=\"\n errorDirective!.templateRef;\n context: {\n $implicit: label(),\n error: vm.control.errors![errorDirective.type()]\n }\n \"\n />\n </div>\n }\n @for (error of vm.errorsToDisplay.fallbackErrors; track $index) {\n <div [class]=\"errorClasses\">\n <ng-container\n *ngTemplateOutlet=\"\n vm.errorsToDisplay.fallback!.templateRef;\n context: {\n $implicit: label(),\n type: error.type,\n error: error.value\n }\n \"\n />\n </div>\n }\n}\n" }]
}], propDecorators: { control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], errorDirectives: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ValidationErrorDirective), { isSignal: true }] }], fallbackDirective: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ValidationFallbackDirective), { isSignal: true }] }] } });
const NO_ERRORS = {
shouldDisplayErrors: false
};
/**
* **Experimental**
*
* Component allowing to display validation error messages associated to a given field of a signal form.
* The control is provided using the `forField` input of the component.
*
* Example usage:
* ```
* <val-signal-errors [forField]="form.birthYear">
* <ng-template valError="required">The birth year is mandatory</ng-template>
* <ng-template valError="max" let-error="error">The max value for the birth year is {{ error.max | number }}</ng-template>
* </val-errors>
* ```
*
* This component, if the control is invalid, displays its validation errors using the provided templates.
* The templates, as shown in the above example, have access to the validation error itself.
*
* The label of the control can also be provided as input, and then used in the templates:
* ```
* <val-signal-errors [forField]="form.birthYear" label="the birth year">
* <ng-template valError="required" let-label>{{ label }} is mandatory</ng-template>
* <ng-template valError="max" let-error="error" let-label>The max value for {{ label }} is {{ error.max | number }}</ng-template>
* </val-signal-errors>
* ```
*
* The component‘s behavior is configured globally by the Config service (see its documentation for more details). It can
* - display the first error, or all the errors
* - add CSS classes to its host `<val-errors>` element
* - add CSS classes to each error message element being displayed
* - choose when to display the errors (dirty, touched, etc.)
*
* Global, default templates can be defined (and used by this component) using the default validation errors directive
* (see its documentation for details). So, if the default error messages are defined and sufficient for a given control, all you
* need is
*
* ```
* <val-signal-errors [forField]="form.birthYear" />
* ```
*
* or, if the default templates expect a label:
*
* ```
* <val-signal-errors [forField]="form.birthYear" label="the birth year" />
* ```
*
* If, however, you want to override one or several error messages by custom ones, you can do so by simply defining them inside the
* component:
*
* ```
* <val-signal-errors [forField]="form.birthYear" label="the birth year">
* <ng-template valError="max">You're too young, sorry</ng-template>
* </val-signal-errors>
* ```
*
* A fallback template can also be provided. This fallback template is used for all the errors that exist on the form control
* but are not handled by any of the specific error templates:
* ```
* <val-signal-errors [forField]="form.birthYear" label="the birth year">
* <ng-template valError="max">You're too young, sorry</ng-template>
* <ng-template valFallback let-label let-type="type" let-error="error">{{ label }} has an unhandled error of kind {{ type }}: {{ error | json }}</ng-template>
* </val-signal-errors>
* ```
* Note that, the fallback template can also be defined in the default validation errors directive (see its documentation for details).
* If a fallback template is defined inside `val-signal-errors`, it overrides the default fallback.
*
* If an error is present on the field, but doesn't have any template, default template or fallback template defined for its type,
* then it's not displayed. If the field is valid, or if none of the errors of the component has a matching template or default template,
* then this component itself is hidden.
*/
class ValidationSignalErrorsComponent {
/**
* The FieldTree containing the validation errors.
*/
// eslint-disable-next-line @angular-eslint/no-input-rename
field = input.required({ ...(ngDevMode ? { debugName: "field" } : {}), alias: 'forField' });
/**
* The label of the field, exposed to templates so they can use it in the error message.
*/
label = input(null, { ...(ngDevMode ? { debugName: "label" } : {}) });
/**
* The list of validation error directives (i.e. <ng-template valError="...">) contained inside the component element.
*/
errorDirectives = contentChildren(ValidationErrorDirective, { ...(ngDevMode ? { debugName: "errorDirectives" } : {}) });
/**
* The validation fallback directive (i.e. <ng-template valFallback>) contained inside the component element.
*/
fallbackDirective = contentChild(ValidationFallbackDirective, { ...(ngDevMode ? { debugName: "fallbackDirective" } : {}) });
/**
* The Config service instance, defining the behavior of this component
*/
config = inject(ValdemortConfig);
errorsClasses = this.config.errorsClasses || '';
errorClasses = this.config.errorClasses || '';
/**
* The DefaultValidationErrors service instance, holding the default error templates,
* optionally defined by using the default validation errors directive
*/
defaultValidationErrors = inject(DefaultValidationErrors);
hasDisplayableError = computed(() => {
const field = this.field();
const errors = field().errors();
return (errors.length > 0 &&
(!!this.fallbackDirective() ||
!!this.defaultValidationErrors.fallback() ||
errors.some(error => this.defaultValidationErrors.directives().some(dir => dir.type() === error.kind) ||
this.errorDirectives().some(dir => dir.type() === error.kind))));
}, { ...(ngDevMode ? { debugName: "hasDisplayableError" } : {}) });
shouldDisplayErrors = computed(() => {
const field = this.field();
const fieldState = field();
if (!fieldState.invalid() || !this.hasDisplayableError()) {
return false;
}
return this.config.shouldDisplayFieldErrors(fieldState);
}, { ...(ngDevMode ? { debugName: "shouldDisplayErrors" } : {}) });
vm = computed(() => {
if (this.shouldDisplayErrors()) {
const errorsToDisplay = this.findErrorsToDisplay();
return {
shouldDisplayErrors: true,
errorsToDisplay
};
}
else {
return NO_ERRORS;
}
}, { ...(ngDevMode ? { debugName: "vm" } : {}) });
findErrorsToDisplay() {
const field = this.field();
const fieldErrors = field().errors();
const mergedErrors = [];
const fallbackErrors = [];
const alreadyMetTypes = new Set();
const shouldContinue = () => this.config.displayMode === DisplayMode.ALL || (mergedErrors.length === 0 && fallbackErrors.length === 0);
const defaultValidationErrorDirectives = this.defaultValidationErrors.directives();
for (let i = 0; i < defaultValidationErrorDirectives.length && shouldContinue(); i++) {
const defaultDirective = defaultValidationErrorDirectives[i];
const error = fieldErrors.find(error => error.kind === defaultDirective.type());
if (error) {
const customDirectiveOfSameType = this.errorDirectives().find(dir => dir.type() === defaultDirective.type());
mergedErrors.push({ directive: customDirectiveOfSameType ?? defaultDirective, error });
}
alreadyMetTypes.add(defaultDirective.type());
}
if (shouldContinue()) {
const customDirectives = this.errorDirectives();
for (let i = 0; i < customDirectives.length && shouldContinue(); i++) {
const customDirective = customDirectives[i];
if (!alreadyMetTypes.has(customDirective.type())) {
const error = fieldErrors.find(error => error.kind === customDirective.type());
if (error) {
mergedErrors.push({ directive: customDirective, error });
}
}
alreadyMetTypes.add(customDirective.type());
}
}
if (shouldContinue() && (this.fallbackDirective() || this.defaultValidationErrors.fallback())) {
for (let i = 0; i < fieldErrors.length && shouldContinue(); i++) {
const error = fieldErrors[i];
if (!alreadyMetTypes.has(error.kind)) {
fallbackErrors.push(error);
}
}
}
return {
errors: mergedErrors,
fallback: this.fallbackDirective() ?? this.defaultValidationErrors.fallback(),
fallbackErrors
};
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValidationSignalErrorsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.2", type: ValidationSignalErrorsComponent, isStandalone: true, selector: "val-signal-errors", inputs: { field: { classPropertyName: "field", publicName: "forField", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "errorsClasses", "style.display": "vm().shouldDisplayErrors ? '' : 'none'" } }, queries: [{ propertyName: "errorDirectives", predicate: ValidationErrorDirective, isSignal: true }, { propertyName: "fallbackDirective", first: true, predicate: ValidationFallbackDirective, descendants: true, isSignal: true }], ngImport: i0, template: "@let vm = this.vm();\n@if (vm.shouldDisplayErrors) {\n @for (errorWithDirective of vm.errorsToDisplay.errors; track $index) {\n <div [class]=\"errorClasses\">\n <ng-container\n *ngTemplateOutlet=\"\n errorWithDirective.directive.templateRef;\n context: {\n $implicit: label(),\n error: errorWithDirective.error\n }\n \"\n />\n </div>\n }\n @for (error of vm.errorsToDisplay.fallbackErrors; track $index) {\n <div [class]=\"errorClasses\">\n <ng-container\n *ngTemplateOutlet=\"\n vm.errorsToDisplay.fallback!.templateRef;\n context: {\n $implicit: label(),\n type: error.kind,\n error: error\n }\n \"\n />\n </div>\n }\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValidationSignalErrorsComponent, decorators: [{
type: Component,
args: [{ selector: 'val-signal-errors', host: {
'[class]': 'errorsClasses',
'[style.display]': `vm().shouldDisplayErrors ? '' : 'none'`
}, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let vm = this.vm();\n@if (vm.shouldDisplayErrors) {\n @for (errorWithDirective of vm.errorsToDisplay.errors; track $index) {\n <div [class]=\"errorClasses\">\n <ng-container\n *ngTemplateOutlet=\"\n errorWithDirective.directive.templateRef;\n context: {\n $implicit: label(),\n error: errorWithDirective.error\n }\n \"\n />\n </div>\n }\n @for (error of vm.errorsToDisplay.fallbackErrors; track $index) {\n <div [class]=\"errorClasses\">\n <ng-container\n *ngTemplateOutlet=\"\n vm.errorsToDisplay.fallback!.templateRef;\n context: {\n $implicit: label(),\n type: error.kind,\n error: error\n }\n \"\n />\n </div>\n }\n}\n" }]
}], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "forField", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], errorDirectives: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ValidationErrorDirective), { isSignal: true }] }], fallbackDirective: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ValidationFallbackDirective), { isSignal: true }] }] } });
class ValdemortModule {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValdemortModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.2", ngImport: i0, type: ValdemortModule, imports: [ValidationErrorsComponent,
ValidationSignalErrorsComponent,
ValidationErrorDirective,
ValidationFallbackDirective,
DefaultValidationErrorsDirective], exports: [ValidationErrorsComponent,
ValidationSignalErrorsComponent,
ValidationErrorDirective,
ValidationFallbackDirective,
DefaultValidationErrorsDirective] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValdemortModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: ValdemortModule, decorators: [{
type: NgModule,
args: [{
imports: [
ValidationErrorsComponent,
ValidationSignalErrorsComponent,
ValidationErrorDirective,
ValidationFallbackDirective,
DefaultValidationErrorsDirective
],
exports: [
ValidationErrorsComponent,
ValidationSignalErrorsComponent,
ValidationErrorDirective,
ValidationFallbackDirective,
DefaultValidationErrorsDirective
]
}]
}] });
/*
* Public API Surface of ngx-valdemort
*/
/**
* Generated bundle index. Do not edit.
*/
export { DefaultValidationErrorsDirective, DisplayMode, ValdemortConfig, ValdemortModule, ValidationErrorDirective, ValidationErrorsComponent, ValidationFallbackDirective, ValidationSignalErrorsComponent };
//# sourceMappingURL=ngx-valdemort.mjs.map