From 58913041a9394147c50ca3d477b3076695fd3f28 Mon Sep 17 00:00:00 2001 From: Abdurrahman Ekinci Date: Wed, 13 Aug 2025 13:54:57 +0200 Subject: [PATCH 1/3] feat(ngx-forms): Added ngxFormsErrorsCustomErrorMessages input to override global error messages on a per-control basis, enabling context-specific wording and dynamic translations --- .../error/error.component.abstract.ts | 4 ++ .../lib/directives/errors/errors.directive.ts | 46 +++++++++++++++---- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/libs/angular/forms/src/lib/abstracts/error/error.component.abstract.ts b/libs/angular/forms/src/lib/abstracts/error/error.component.abstract.ts index 765c3cc4..93dfdff8 100644 --- a/libs/angular/forms/src/lib/abstracts/error/error.component.abstract.ts +++ b/libs/angular/forms/src/lib/abstracts/error/error.component.abstract.ts @@ -15,4 +15,8 @@ export class NgxFormsErrorAbstractComponent { * The error object provided by the control */ @Input({ required: true }) public data: ValidationErrors; + /** + * An object containing custom error messages + */ + @Input() public customErrorMessages: Record; } diff --git a/libs/angular/forms/src/lib/directives/errors/errors.directive.ts b/libs/angular/forms/src/lib/directives/errors/errors.directive.ts index 382f5d9a..0286ae1b 100644 --- a/libs/angular/forms/src/lib/directives/errors/errors.directive.ts +++ b/libs/angular/forms/src/lib/directives/errors/errors.directive.ts @@ -19,7 +19,7 @@ import { ValidationErrors, } from '@angular/forms'; -import { Observable, Subject, combineLatest, startWith, takeUntil, tap } from 'rxjs'; +import { Subject, combineLatest, startWith, takeUntil, tap } from 'rxjs'; import { NgxFormsErrorsConfigurationToken } from '../../tokens'; import { NgxFormsErrorConfigurationOptions } from '../../interfaces'; import { NgxFormsErrorAbstractComponent } from '../../abstracts'; @@ -32,7 +32,7 @@ import { touchedEventListener } from '../../utils'; export class NgxFormsErrorsDirective implements AfterViewInit, OnDestroy { // Iben: Handle the OnDestroy flow private readonly onDestroySubject$ = new Subject(); - private readonly onDestroy$ = new Observable(); + private readonly onDestroy$ = this.onDestroySubject$.asObservable(); /** * The actual template of the input element @@ -59,25 +59,39 @@ export class NgxFormsErrorsDirective implements AfterViewInit, OnDestroy { */ private componentRef: ComponentRef; + /** + * Custom error messages to override default ones + */ + private _customErrorMessages: Record; + /** * A reference to a control or a string reference to the control */ @Input('ngxFormsErrors') public control: AbstractControl | string; + /** + * Custom error messages to override default ones + */ + @Input('ngxFormsErrorsCustomErrorMessages') + public set customErrorMessages(value: Record) { + this._customErrorMessages = value ?? {}; + } constructor( @Optional() private readonly formGroupDirective: FormGroupDirective, @Optional() private readonly formNameDirective: FormGroupName, + @Optional() private readonly templateRef: TemplateRef, @Optional() @Inject(NgxFormsErrorsConfigurationToken) private readonly config: NgxFormsErrorConfigurationOptions, private readonly viewContainer: ViewContainerRef, private readonly elementRef: ElementRef, private readonly renderer: Renderer2, - private readonly templateRef: TemplateRef, private readonly cdRef: ChangeDetectorRef ) { // Iben: Set the current template ref at constructor time so we actually have the provided template (as done in the *ngIf directive) - this.template = this.templateRef; + if (this.templateRef) { + this.template = this.templateRef; + } } public ngOnDestroy(): void { @@ -89,7 +103,10 @@ export class NgxFormsErrorsDirective implements AfterViewInit, OnDestroy { public ngAfterViewInit(): void { // Iben: Render the actual input so that it is always visible this.viewContainer.clear(); - this.viewContainer.createEmbeddedView(this.template); + + if (this.template) { + this.viewContainer.createEmbeddedView(this.template); + } // Iben: If no control was provided, we early exit and log an error if (!this.control) { @@ -173,11 +190,17 @@ export class NgxFormsErrorsDirective implements AfterViewInit, OnDestroy { this.errorComponent = this.componentRef.instance; // Iben: Set the data of the error component - const { errors, errorKeys, data } = this.getErrors(this.abstractControl.errors); + const { errorKeys, data } = this.getErrors(this.abstractControl.errors); + + // Abdurrahman: Merge defaults with custom overrides if provided + const errors = errorKeys.map( + (key) => this._customErrorMessages?.[key] || this.config.errors[key] + ); this.errorComponent.errors = errors; this.errorComponent.errorKeys = errorKeys; this.errorComponent.data = data; + this.errorComponent.customErrorMessages = this._customErrorMessages; } /** @@ -203,12 +226,15 @@ export class NgxFormsErrorsDirective implements AfterViewInit, OnDestroy { this.renderer.setAttribute(this.errorsElement, 'class', 'ngx-forms-error'); // Iben: Set the errors based on the keys - this.renderer.setProperty( - this.errorsElement, - 'innerHTML', - this.getErrors(this.abstractControl.errors).errors.join(', ') + const { errorKeys } = this.getErrors(this.abstractControl.errors); + + // Abdurrahman: Merge defaults with custom overrides if provided + const errors = errorKeys.map( + (key) => this._customErrorMessages?.[key] || this.config.errors[key] ); + this.renderer.setProperty(this.errorsElement, 'textContent', errors.join(', ')); + // Iben: insert the paragraph underneath the input component this.renderer.insertBefore( this.elementRef.nativeElement.parentNode, From b3ec959042d4855afbe389841b9cd39a41ba629b Mon Sep 17 00:00:00 2001 From: Abdurrahman Ekinci Date: Thu, 14 Aug 2025 03:04:29 +0200 Subject: [PATCH 2/3] feat(ngx-forms): renamed underscored property and extended test to cover new features --- .../errors/errors.directive.spec.ts | 253 +++++++++++++++++- .../lib/directives/errors/errors.directive.ts | 11 +- 2 files changed, 256 insertions(+), 8 deletions(-) diff --git a/libs/angular/forms/src/lib/directives/errors/errors.directive.spec.ts b/libs/angular/forms/src/lib/directives/errors/errors.directive.spec.ts index 87f6d453..55f23d0e 100644 --- a/libs/angular/forms/src/lib/directives/errors/errors.directive.spec.ts +++ b/libs/angular/forms/src/lib/directives/errors/errors.directive.spec.ts @@ -58,10 +58,85 @@ export class FormErrorComponent extends NgxFormsErrorAbstractComponent {} describe('NgxFormsErrorsDirective', () => { const errors = { - required: 'Dit veld is verplicht', - email: 'Dit veld is geen e-mail', - minlength: 'Dit veld moet minstens 3 lang zijn', + required: 'This field is required', + email: 'This field is not a valid email', + minlength: 'This field must be at least 3 characters long', }; + + @Component({ + selector: 'kp-attr-usage', + template: ` + + + `, + imports: [ReactiveFormsModule, NgxFormsErrorsDirective], + }) + class AttributeUsageComponent { + public control = new FormControl('', [Validators.email, Validators.minLength(10)]); + } + + @Component({ + selector: 'kp-multi-errors', + template: ` + + + + + `, + imports: [ReactiveFormsModule, NgxFormsErrorsDirective], + }) + class MultiErrorsComponent extends FormAccessor { + initForm() { + return new FormGroup({ + multi: new FormControl('', [Validators.email, Validators.minLength(10)]), + }); + } + } + + @Component({ + selector: 'kp-multi-errors-component', + template: ` + + + + `, + imports: [ReactiveFormsModule, NgxFormsErrorsDirective, FormErrorComponent], + }) + class MultiErrorsWithComponent extends FormAccessor { + initForm() { + return new FormGroup({ + multi: new FormControl('', [Validators.email, Validators.minLength(10)]), + }); + } + } + + @Component({ + selector: 'kp-no-control', + template: ` + + + `, + imports: [ReactiveFormsModule, NgxFormsErrorsDirective], + }) + class NoControlProvidedComponent { + public dummy = new FormControl(''); + } + + @Component({ + selector: 'kp-invalid-string', + template: ` + + + + + `, + imports: [ReactiveFormsModule, NgxFormsErrorsDirective], + }) + class InvalidControlStringComponent extends FormAccessor { + initForm() { + return new FormGroup({ exists: new FormControl('') }); + } + } describe('Without component', () => { let fixture: ComponentFixture; @@ -167,4 +242,176 @@ describe('NgxFormsErrorsDirective', () => { expect(errorElements.length).toBe(0); }); }); + + describe('Attribute usage with multiple errors (show variations)', () => { + const multiErrors = { + email: 'Email invalid', + minlength: 'Minimum length not reached', + }; + + describe('show = default (1)', () => { + let fixture: ComponentFixture; + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ReactiveFormsModule, MultiErrorsComponent], + providers: [ + { + provide: NgxFormsErrorsConfigurationToken, + useValue: { showWhen: 'dirty', errors: multiErrors }, + }, + ], + }); + fixture = TestBed.createComponent(MultiErrorsComponent); + fixture.detectChanges(); + }); + + it('should show only the first error when multiple are present by default', () => { + const control = fixture.componentRef.instance.form.get('multi'); + control.setValue('abc'); // triggers email + minlength + control.markAsDirty(); + control.updateValueAndValidity(); + fixture.detectChanges(); + const error = fixture.nativeElement.querySelector('.ngx-forms-error'); + expect(error.textContent).toBe(multiErrors.email); // first validator supplied + }); + }); + + describe('show = 2', () => { + let fixture: ComponentFixture; + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ReactiveFormsModule, MultiErrorsComponent], + providers: [ + { + provide: NgxFormsErrorsConfigurationToken, + useValue: { showWhen: 'dirty', show: 2, errors: multiErrors }, + }, + ], + }); + fixture = TestBed.createComponent(MultiErrorsComponent); + fixture.detectChanges(); + }); + + it('should show both errors when show = 2', () => { + const control = fixture.componentRef.instance.form.get('multi'); + control.setValue('abc'); + control.markAsDirty(); + control.updateValueAndValidity(); + fixture.detectChanges(); + const error = fixture.nativeElement.querySelector('.ngx-forms-error'); + expect(error.textContent).toBe(`${multiErrors.email}, ${multiErrors.minlength}`); + }); + }); + + describe("show = 'all'", () => { + let fixture: ComponentFixture; + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ReactiveFormsModule, MultiErrorsComponent], + providers: [ + { + provide: NgxFormsErrorsConfigurationToken, + useValue: { showWhen: 'dirty', show: 'all', errors: multiErrors }, + }, + ], + }); + fixture = TestBed.createComponent(MultiErrorsComponent); + fixture.detectChanges(); + }); + + it('should show all errors when show = all', () => { + const control = fixture.componentRef.instance.form.get('multi'); + control.setValue('abc'); + control.markAsDirty(); + control.updateValueAndValidity(); + fixture.detectChanges(); + const error = fixture.nativeElement.querySelector('.ngx-forms-error'); + expect(error.textContent).toBe(`${multiErrors.email}, ${multiErrors.minlength}`); + }); + }); + }); + + describe('Custom error messages override', () => { + const baseErrors = { + email: 'Base email', + minlength: 'Base minlength', + }; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ReactiveFormsModule, MultiErrorsWithComponent, FormErrorComponent], + providers: [ + { + provide: NgxFormsErrorsConfigurationToken, + useValue: { + showWhen: 'dirty', + show: 'all', + errors: baseErrors, + component: FormErrorComponent, + }, + }, + ], + }); + fixture = TestBed.createComponent(MultiErrorsWithComponent); + // Provide custom overrides dynamically + const inputEl: HTMLInputElement = fixture.nativeElement.querySelector('input'); + // Patch the directive instance to set customErrorMessages input + const directiveInstance: any = (fixture.debugElement.childNodes as any[]) + .map((n) => n.injector?.get?.(NgxFormsErrorsDirective, null)) + .filter(Boolean)[0]; + if (directiveInstance) { + directiveInstance.customErrorMessages = { email: 'Custom email override' }; + } + fixture.detectChanges(); + }); + + it('should use custom message overrides when provided (component flow)', () => { + const control = fixture.componentRef.instance.form.get('multi'); + control.setValue('abc'); + control.markAsDirty(); + control.updateValueAndValidity(); + fixture.detectChanges(); + const errorCmp = fixture.nativeElement.querySelector('.kp-error'); + expect(errorCmp.textContent).toContain('Custom email override'); + }); + }); + + describe('Early exit & error logging scenarios', () => { + it('logs an error when no control input is provided', () => { + spyOn(console, 'error'); + TestBed.configureTestingModule({ + imports: [ReactiveFormsModule, NoControlProvidedComponent], + providers: [ + { + provide: NgxFormsErrorsConfigurationToken, + useValue: { showWhen: 'dirty', errors }, + }, + ], + }); + const fixture = TestBed.createComponent(NoControlProvidedComponent); + fixture.detectChanges(); + expect(console.error).toHaveBeenCalled(); + const errEl = fixture.nativeElement.querySelector('.ngx-forms-error'); + expect(errEl).toBeNull(); + }); + + it('logs an error when provided control string does not resolve', () => { + spyOn(console, 'error'); + TestBed.configureTestingModule({ + imports: [ReactiveFormsModule, InvalidControlStringComponent], + providers: [ + { + provide: NgxFormsErrorsConfigurationToken, + useValue: { showWhen: 'dirty', errors }, + }, + ], + }); + const fixture = TestBed.createComponent(InvalidControlStringComponent); + fixture.detectChanges(); + expect(console.error).toHaveBeenCalled(); + const errEl = fixture.nativeElement.querySelector('.ngx-forms-error'); + expect(errEl).toBeNull(); + }); + }); }); diff --git a/libs/angular/forms/src/lib/directives/errors/errors.directive.ts b/libs/angular/forms/src/lib/directives/errors/errors.directive.ts index 0286ae1b..f7a39f10 100644 --- a/libs/angular/forms/src/lib/directives/errors/errors.directive.ts +++ b/libs/angular/forms/src/lib/directives/errors/errors.directive.ts @@ -62,7 +62,7 @@ export class NgxFormsErrorsDirective implements AfterViewInit, OnDestroy { /** * Custom error messages to override default ones */ - private _customErrorMessages: Record; + private customMessages: Record; /** * A reference to a control or a string reference to the control @@ -73,7 +73,7 @@ export class NgxFormsErrorsDirective implements AfterViewInit, OnDestroy { */ @Input('ngxFormsErrorsCustomErrorMessages') public set customErrorMessages(value: Record) { - this._customErrorMessages = value ?? {}; + this.customMessages = value ?? {}; } constructor( @@ -104,6 +104,7 @@ export class NgxFormsErrorsDirective implements AfterViewInit, OnDestroy { // Iben: Render the actual input so that it is always visible this.viewContainer.clear(); + // Abdurrahman: Only render template if this directive is used in structural form if (this.template) { this.viewContainer.createEmbeddedView(this.template); } @@ -194,13 +195,13 @@ export class NgxFormsErrorsDirective implements AfterViewInit, OnDestroy { // Abdurrahman: Merge defaults with custom overrides if provided const errors = errorKeys.map( - (key) => this._customErrorMessages?.[key] || this.config.errors[key] + (key) => this.customMessages?.[key] || this.config.errors[key] ); this.errorComponent.errors = errors; this.errorComponent.errorKeys = errorKeys; this.errorComponent.data = data; - this.errorComponent.customErrorMessages = this._customErrorMessages; + this.errorComponent.customErrorMessages = this.customMessages; } /** @@ -230,7 +231,7 @@ export class NgxFormsErrorsDirective implements AfterViewInit, OnDestroy { // Abdurrahman: Merge defaults with custom overrides if provided const errors = errorKeys.map( - (key) => this._customErrorMessages?.[key] || this.config.errors[key] + (key) => this.customMessages?.[key] || this.config.errors[key] ); this.renderer.setProperty(this.errorsElement, 'textContent', errors.join(', ')); From 04bd3ae69c5ab118b32e2d9b0184c08332efec2d Mon Sep 17 00:00:00 2001 From: Abdurrahman Ekinci Date: Thu, 14 Aug 2025 03:07:06 +0200 Subject: [PATCH 3/3] feat(ngx-errors): updated docs and demo --- .../demos/ngxerrors/error/error.component.ts | 12 ++- .../ngxerrors/form-accessor.component.html | 26 +++++-- .../ngxerrors/form-accessor.component.ts | 6 +- .../ngxerrors/ngxerrors.demo.component.ts | 6 +- .../forms/implementation/ngxerrors/index.md | 74 ++++++++++++++++++- 5 files changed, 114 insertions(+), 10 deletions(-) diff --git a/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/error/error.component.ts b/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/error/error.component.ts index ef50e6e9..d3b9e655 100644 --- a/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/error/error.component.ts +++ b/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/error/error.component.ts @@ -1,10 +1,20 @@ import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; import { NgxFormsErrorAbstractComponent } from '@ngx/forms'; @Component({ selector: 'app-form-error', - template: `This is the error: {{ errors[0] }}`, + template: ` + @if (errors.length) { +
    + @for (error of errors; track error) { +
  • {{ error }}
  • + } +
+ } + `, styleUrls: ['./error.component.scss'], standalone: true, + imports: [CommonModule], }) export class FormErrorComponent extends NgxFormsErrorAbstractComponent {} diff --git a/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/form-accessor.component.html b/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/form-accessor.component.html index 9903db33..85eff76c 100644 --- a/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/form-accessor.component.html +++ b/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/form-accessor.component.html @@ -1,10 +1,26 @@ -

Hello

+ +

Hello (structural)

-

World

- + +

World (attribute w/ overrides)

+ -

Date

- + +

Date (custom component + override)

+
diff --git a/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/form-accessor.component.ts b/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/form-accessor.component.ts index ac4b07cc..99524e84 100644 --- a/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/form-accessor.component.ts +++ b/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/form-accessor.component.ts @@ -14,7 +14,11 @@ export class FormAccessorComponent extends FormAccessor { initForm() { return new FormGroup({ hello: new FormControl(null, [Validators.required]), - world: new FormControl(null, [Validators.required, Validators.minLength(3)]), + world: new FormControl(null, [ + Validators.required, + Validators.minLength(3), + Validators.pattern(/^[A-Z].*/), + ]), date: new FormControl(null, Validators.required), }); } diff --git a/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/ngxerrors.demo.component.ts b/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/ngxerrors.demo.component.ts index 305fff4c..c2c0f064 100644 --- a/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/ngxerrors.demo.component.ts +++ b/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/demos/ngxerrors/ngxerrors.demo.component.ts @@ -15,13 +15,17 @@ import { FormAccessorContainer, NgxFormsErrorsConfigurationToken } from '@ngx/fo { provide: NgxFormsErrorsConfigurationToken, useValue: { + // Global (default) error messages. Individual controls can override these. errors: { required: 'This field is required', - minlength: 'Too short', + minlength: 'Value is too short (min 3 chars)', + pattern: 'Must start with an uppercase letter', dependedDates: 'Something broke', }, component: FormErrorComponent, showWhen: 'touched', + // Showcase multiple errors rendering + show: 'all', }, }, ], diff --git a/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/index.md b/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/index.md index 11de3575..502aa29a 100644 --- a/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/index.md +++ b/apps/docs/src/app/pages/docs/angular/forms/implementation/ngxerrors/index.md @@ -8,6 +8,17 @@ Intended to be used in projects that require consistent error messages throughou The error message is always rendered right below the element the `ngxErrors` directive is placed on. +--- + +## Why use `NgxFormsErrorsDirective`? + +- **Consistency** – Define your application's error messages once at a root level. +- **Flexibility** – Render errors using the default DOM element or your own custom component. +- **Context-aware** – Override default error messages for specific form controls without changing the global configuration. +- **Integration-friendly** – Works with both _structural directive_ syntax (`*ngxFormsErrors`) and _attribute syntax_ (`[ngxFormsErrors]`). + +--- + ## Configuration To implement the `ngxErrors` directive, we have to provide the necessary configuration on root level and import the `NgxFormsErrorsDirective` where used. @@ -15,7 +26,7 @@ To implement the `ngxErrors` directive, we have to provide the necessary configu A simple example is shown below. ```ts - // Root + // Root module or standalone bootstrap provide providers: [ { provide: NgxFormsErrorsConfigurationToken, @@ -24,7 +35,7 @@ A simple example is shown below. required: 'This is a required field.', email: 'This field is not a valid email address.' }, - showWhen: 'touched', + showWhen: 'touched', // or 'dirty' } }, ] @@ -37,6 +48,13 @@ A simple example is shown below. }) ``` +- `errors`: A mapping between Angular validation error keys and your display messages. +- `showWhen`: Determines when errors are displayed (`'touched'` or `'dirty'`). +- `component` _(optional)_: Provide a custom component to render errors instead of the default `

` element. +- `show` _(optional)_: Number of errors to display or `'all'` to show all. + +--- + ## Basic implementation By default, only two properties are required when setting up the `NgxFormsErrorsDirective`. @@ -47,6 +65,10 @@ The `showWhen` property will determine when an error message becomes visible. Yo Once configured, all we need to do is attach the directive where we wish to render the error. We suggest attaching this directly to the input or your custom input component. +You can attach the directive to an element in two ways: + +**Structural syntax** (renders the input inside the directive’s view): + ```html

Hello

@@ -54,6 +76,17 @@ Once configured, all we need to do is attach the directive where we wish to rend ``` +**Attribute syntax** (applies directive directly to an existing element): + +```html + +``` + +In both cases, `ngxFormsErrors` accepts either: + +- A **string** key that matches the control name in the parent `FormGroup`. +- An **`AbstractControl`** instance directly. + The `ngxFormsErrors` directive allows for a string value that matches with the provided control in a `FormGroup`. Alternatively, you can also pass the `AbstractControl` directly. By using this approach, when the control is invalid and in our case `touched`, the directive will render a `p` element with the `ngx-forms-error` class underneath the input. @@ -89,12 +122,49 @@ The second Input is the `errorKeys` input, which provides us with an array of ke On top of that, the `data` input provides us with the actual `ValidationErrors` on the control. +The `customErrorMessages` input will contain the per-control overrides if they were provided via `ngxFormsErrorsCustomErrorMessages`. + +## Custom error messages per control + +In addition to global messages configured at root level, you can now override them **per control** using the `ngxFormsErrorsCustomErrorMessages` input. + +This is useful when a certain form field requires a different tone, extra context, or localized phrasing without affecting other fields. + +```html + +``` + +If a key is provided in `ngxFormsErrorsCustomErrorMessages`, it will take priority over the global configuration for that control. Any keys not overridden will still fall back to the global `errors` record. + +This works for both the default `

` element output and custom components. + ## Multiple errors By default, the directive only renders a single error, the first one that gets provided in the validation errors object. If we wish to show more errors, we can provide the `show` property in the configuration. We can either provide a specific number of errors we wish to see or provide the option `all` to see all errors. +```ts +{ + provide: NgxFormsErrorsConfigurationToken, + useValue: { + errors, + showWhen: 'touched', + show: 'all' // or a number + } +} +``` + +--- + ## Example {{ NgDocActions.demo("NgxerrorsDemoComponent", { expanded: true }) }}