From 831d85b87a93e1b71155581934e205bb53e272b1 Mon Sep 17 00:00:00 2001
From: Erika McVey <50454925+Blackbaud-ErikaMcVey@users.noreply.github.com>
Date: Thu, 1 Feb 2024 10:07:37 -0500
Subject: [PATCH 1/2] fix(components/forms): refactor form errors
---
.../forms/input-box/basic/demo.component.html | 32 +++--
.../input-box/basic/demo.component.spec.ts | 33 +++--
.../forms/input-box/basic/demo.component.ts | 9 +-
.../forms/input-box/input-box.component.html | 10 ++
.../forms/input-box/input-box.component.ts | 16 +++
libs/components/forms/src/index.ts | 3 +
.../form-error/form-error.component.ts | 37 +++++-
.../modules/form-error/form-errors-token.ts | 3 +
.../form-error/form-errors.component.html | 26 ++--
.../form-error/form-errors.component.scss | 1 +
.../input-box/input-box.component.html | 1 +
.../modules/input-box/input-box.component.ts | 6 +
.../lib/modules/input-box/input-box.module.ts | 8 +-
.../src/form-error/form-error-harness.ts | 11 +-
.../form-error/form-errors-harness.spec.ts | 89 +++++--------
.../src/form-error/form-errors-harness.ts | 105 +++------------
.../input-box-harness-test.component.html | 49 +++++++
.../input-box-harness-test.component.ts | 6 +
.../fixtures/input-box-harness-test.module.ts | 5 +
.../src/input-box/input-box-harness.spec.ts | 122 ++++++++++++++++++
.../src/input-box/input-box-harness.ts | 68 ++++++++++
21 files changed, 430 insertions(+), 210 deletions(-)
create mode 100644 libs/components/forms/src/lib/modules/form-error/form-errors-token.ts
diff --git a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.html b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.html
index 2f5f2f99d3..46842d9304 100644
--- a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.html
+++ b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.html
@@ -16,8 +16,17 @@
New member form
-
-
+
+
@@ -69,18 +78,15 @@ New member form
-
+
-
-
-
- {{ colorError.message }}
-
-
+
+
+ Blur is not a color.
+
diff --git a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.spec.ts
index a348799fb8..a6690a2409 100644
--- a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.spec.ts
@@ -1,6 +1,7 @@
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { SkyAppTestUtility } from '@skyux-sdk/testing';
import { SkyInputBoxHarness } from '@skyux/forms/testing';
import { DemoComponent } from './demo.component';
@@ -40,6 +41,22 @@ describe('Basic input box demo', () => {
});
});
+ describe('last name field', () => {
+ it('should have last name required', async () => {
+ const harness = await setupTest({
+ dataSkyId: 'input-box-last-name',
+ });
+ const inputEl = document.querySelector(
+ 'input.last-name-input-box',
+ ) as HTMLInputElement;
+ inputEl.value = '';
+ SkyAppTestUtility.fireDomEvent(inputEl, 'input');
+ SkyAppTestUtility.fireDomEvent(inputEl, 'blur');
+
+ await expectAsync(harness.hasRequiredError()).toBeResolvedTo(true);
+ });
+ });
+
describe('bio field', () => {
it('should have a character limit of 250', async () => {
const harness = await setupTest({
@@ -71,7 +88,7 @@ describe('Basic input box demo', () => {
});
describe('favorite color field', () => {
- it('should not allow bird to be selected', async () => {
+ it('should not allow blur to be selected', async () => {
const harness = await setupTest({
dataSkyId: 'input-box-favorite-color',
});
@@ -80,19 +97,11 @@ describe('Basic input box demo', () => {
'.input-box-favorite-color-select',
) as HTMLSelectElement;
- selectEl.value = 'bird';
+ selectEl.value = 'blur';
selectEl.dispatchEvent(new Event('change'));
- const customErrors = await harness.getCustomErrors();
-
- expect(customErrors.length).toBe(1);
-
- const birdError = customErrors[0];
-
- await expectAsync(birdError.getDescriptionType()).toBeResolvedTo('error');
- await expectAsync(birdError.getIndicatorType()).toBeResolvedTo('danger');
- await expectAsync(birdError.getText()).toBeResolvedTo(
- 'Bird is not a color.',
+ await expectAsync(harness.hasCustomFormError('color')).toBeResolvedTo(
+ true,
);
});
});
diff --git a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.ts
index c6f2f3abc0..17dbd1c267 100644
--- a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.ts
@@ -44,13 +44,8 @@ export class DemoComponent {
constructor() {
this.favoriteColor = new FormControl('none', [
(control): ValidationErrors | null => {
- if (control.value === 'bird') {
- return {
- color: {
- invalid: true,
- message: 'Bird is not a color.',
- },
- };
+ if (control.value === 'blur') {
+ return { color: true };
}
return null;
diff --git a/apps/playground/src/app/components/forms/input-box/input-box.component.html b/apps/playground/src/app/components/forms/input-box/input-box.component.html
index 7e113fea6c..2a5f1e10e7 100644
--- a/apps/playground/src/app/components/forms/input-box/input-box.component.html
+++ b/apps/playground/src/app/components/forms/input-box/input-box.component.html
@@ -245,6 +245,16 @@
+
+
Help content from template
diff --git a/libs/components/forms/testing/src/input-box/fixtures/input-box-harness-test.component.ts b/libs/components/forms/testing/src/input-box/fixtures/input-box-harness-test.component.ts
index baa02a6c51..3f9b9beaa7 100644
--- a/libs/components/forms/testing/src/input-box/fixtures/input-box-harness-test.component.ts
+++ b/libs/components/forms/testing/src/input-box/fixtures/input-box-harness-test.component.ts
@@ -11,6 +11,7 @@ import {
})
export class InputBoxHarnessTestComponent {
public myForm: UntypedFormGroup;
+ public directiveErrorForm: UntypedFormGroup;
@ViewChild('helpContentTemplate', {
read: TemplateRef,
@@ -32,5 +33,10 @@ export class InputBoxHarnessTestComponent {
firstName: new UntypedFormControl('John'),
lastName: new UntypedFormControl('Doe'),
});
+ this.directiveErrorForm = formBuilder.group({
+ easyModeDatepicker: new UntypedFormControl('123'),
+ easyModeTimepicker: new UntypedFormControl('abc'),
+ easyModePhoneField: new UntypedFormControl('abc'),
+ });
}
}
diff --git a/libs/components/forms/testing/src/input-box/fixtures/input-box-harness-test.module.ts b/libs/components/forms/testing/src/input-box/fixtures/input-box-harness-test.module.ts
index 26b4933f3d..08ca311bf2 100644
--- a/libs/components/forms/testing/src/input-box/fixtures/input-box-harness-test.module.ts
+++ b/libs/components/forms/testing/src/input-box/fixtures/input-box-harness-test.module.ts
@@ -1,8 +1,10 @@
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { SkyIdModule } from '@skyux/core';
+import { SkyDatepickerModule, SkyTimepickerModule } from '@skyux/datetime';
import { SkyInputBoxModule } from '@skyux/forms';
import { SkyStatusIndicatorModule } from '@skyux/indicators';
+import { SkyPhoneFieldModule } from '@skyux/phone-field';
import { InputBoxHarnessTestComponent } from './input-box-harness-test.component';
@@ -13,6 +15,9 @@ import { InputBoxHarnessTestComponent } from './input-box-harness-test.component
SkyIdModule,
SkyInputBoxModule,
SkyStatusIndicatorModule,
+ SkyDatepickerModule,
+ SkyTimepickerModule,
+ SkyPhoneFieldModule,
],
declarations: [InputBoxHarnessTestComponent],
})
diff --git a/libs/components/forms/testing/src/input-box/input-box-harness.spec.ts b/libs/components/forms/testing/src/input-box/input-box-harness.spec.ts
index b349563143..8539749f63 100644
--- a/libs/components/forms/testing/src/input-box/input-box-harness.spec.ts
+++ b/libs/components/forms/testing/src/input-box/input-box-harness.spec.ts
@@ -2,6 +2,7 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Validators } from '@angular/forms';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { SkyValidators } from '@skyux/validation';
import { InputBoxHarnessTestComponent } from './fixtures/input-box-harness-test.component';
import { InputBoxHarnessTestModule } from './fixtures/input-box-harness-test.module';
@@ -138,6 +139,127 @@ describe('Input box harness', () => {
await expectAsync(customError.getText()).toBeResolvedTo('Test error');
});
+ it('should return whether custom form error has fired', async () => {
+ const { inputBoxHarness } = await setupTest({
+ dataSkyId: 'custom-error-easy-mode',
+ });
+
+ await expectAsync(
+ inputBoxHarness.hasCustomFormError('custom'),
+ ).toBeResolvedTo(true);
+ });
+
+ it('should return whether required error has fired', async () => {
+ const { component, fixture, inputBoxHarness } = await setupTest({
+ dataSkyId: 'my-input-box-last-name-easy-mode',
+ });
+
+ const control = component.myForm.controls['lastName'];
+ control.addValidators(Validators.required);
+ control.setValue('');
+ control.markAsDirty();
+
+ fixture.detectChanges();
+
+ await expectAsync(inputBoxHarness.hasRequiredError()).toBeResolvedTo(true);
+ });
+
+ it('should return whether minimum length error has fired', async () => {
+ const { component, fixture, inputBoxHarness } = await setupTest({
+ dataSkyId: 'my-input-box-last-name-easy-mode',
+ });
+
+ const control = component.myForm.controls['lastName'];
+ control.addValidators(Validators.minLength(2));
+ control.setValue('a');
+ control.markAsDirty();
+
+ fixture.detectChanges();
+
+ await expectAsync(inputBoxHarness.hasMinLengthError()).toBeResolvedTo(true);
+ });
+
+ it('should return whether maximum length error has fired', async () => {
+ const { component, fixture, inputBoxHarness } = await setupTest({
+ dataSkyId: 'my-input-box-last-name-easy-mode',
+ });
+
+ const control = component.myForm.controls['lastName'];
+ control.addValidators(Validators.maxLength(1));
+ control.setValue('abc');
+ control.markAsDirty();
+
+ fixture.detectChanges();
+
+ await expectAsync(inputBoxHarness.hasMaxLengthError()).toBeResolvedTo(true);
+ });
+
+ it('should return whether email validator error has fired', async () => {
+ const { component, fixture, inputBoxHarness } = await setupTest({
+ dataSkyId: 'my-input-box-last-name-easy-mode',
+ });
+
+ const control = component.myForm.controls['lastName'];
+ control.addValidators(SkyValidators.email);
+ control.setValue('abc');
+ control.markAsDirty();
+
+ fixture.detectChanges();
+
+ await expectAsync(inputBoxHarness.hasEmailError()).toBeResolvedTo(true);
+ });
+
+ it('should return whether url validator error has fired', async () => {
+ const { component, fixture, inputBoxHarness } = await setupTest({
+ dataSkyId: 'my-input-box-last-name-easy-mode',
+ });
+
+ const control = component.myForm.controls['lastName'];
+ control.addValidators(SkyValidators.url);
+ control.setValue('abc');
+ control.markAsDirty();
+
+ fixture.detectChanges();
+
+ await expectAsync(inputBoxHarness.hasUrlError()).toBeResolvedTo(true);
+ });
+
+ it('should return whether date picker validator error has fired', async () => {
+ const { fixture, inputBoxHarness } = await setupTest({
+ dataSkyId: 'datepicker-easy-mode',
+ });
+
+ fixture.detectChanges();
+
+ await expectAsync(inputBoxHarness.hasDateError()).toBeResolvedTo(true);
+ });
+
+ it('should return whether time picker validator error has fired', async () => {
+ const { component, fixture, inputBoxHarness } = await setupTest({
+ dataSkyId: 'timepicker-easy-mode',
+ });
+
+ const control = component.directiveErrorForm.controls['easyModeTimepicker'];
+ control.markAsDirty();
+ fixture.detectChanges();
+
+ await expectAsync(inputBoxHarness.hasTimeError()).toBeResolvedTo(true);
+ });
+
+ it('should return whether phone field validator error has fired', async () => {
+ const { component, fixture, inputBoxHarness } = await setupTest({
+ dataSkyId: 'phone-field-easy-mode',
+ });
+
+ const control = component.directiveErrorForm.controls['easyModePhoneField'];
+ control.markAsDirty();
+ fixture.detectChanges();
+
+ await expectAsync(inputBoxHarness.hasPhoneFieldError()).toBeResolvedTo(
+ true,
+ );
+ });
+
it('should return character counter indicator', async () => {
const { component, fixture, inputBoxHarness } = await setupTest({
dataSkyId: DATA_SKY_ID_EASY_MODE,
diff --git a/libs/components/forms/testing/src/input-box/input-box-harness.ts b/libs/components/forms/testing/src/input-box/input-box-harness.ts
index 01aefb6fcc..dc00ac57c8 100644
--- a/libs/components/forms/testing/src/input-box/input-box-harness.ts
+++ b/libs/components/forms/testing/src/input-box/input-box-harness.ts
@@ -9,6 +9,7 @@ import { SkyStatusIndicatorHarness } from '@skyux/indicators/testing';
import { SkyPopoverHarness } from '@skyux/popovers/testing';
import { SkyCharacterCounterIndicatorHarness } from '../character-counter/character-counter-indicator-harness';
+import { SkyFormErrorsHarness } from '../public-api';
import { SkyInputBoxHarnessFilters } from './input-box-harness-filters';
@@ -25,6 +26,10 @@ export class SkyInputBoxHarness extends SkyComponentHarness {
#getLabel = this.locatorForOptional('.sky-control-label');
#getWrapper = this.locatorFor('.sky-input-box');
+ async #getFormError(): Promise {
+ return this.locatorFor(SkyFormErrorsHarness)();
+ }
+
/**
* Gets a `HarnessPredicate` that can be used to search for a
* `SkyInputBoxHarness` that meets certain criteria.
@@ -79,6 +84,69 @@ export class SkyInputBoxHarness extends SkyComponentHarness {
return errors;
}
+ /**
+ * Whether the custom error is triggered.
+ */
+ public async hasCustomFormError(errorName: string): Promise {
+ return (await this.#getFormError()).hasError(errorName);
+ }
+
+ /**
+ * Whether the required field is empty.
+ */
+ public async hasRequiredError(): Promise {
+ return (await this.#getFormError()).hasError('required');
+ }
+
+ /**
+ * Whether the field has more characters than allowed.
+ */
+ public async hasMaxLengthError(): Promise {
+ return (await this.#getFormError()).hasError('maxlength');
+ }
+
+ /**
+ * Whether the field has fewer characters than allowed.
+ */
+ public async hasMinLengthError(): Promise {
+ return (await this.#getFormError()).hasError('minlength');
+ }
+
+ /*
+ * Whether the field is set to an invalid email address.
+ */
+ public async hasEmailError(): Promise {
+ return (await this.#getFormError()).hasError('email');
+ }
+
+ /*
+ * Whether the field is set to an invalid URL.
+ */
+ public async hasUrlError(): Promise {
+ return (await this.#getFormError()).hasError('url');
+ }
+
+ /*
+ * Whether the field is set to an invalid date.
+ */
+ public async hasDateError(): Promise {
+ return (await this.#getFormError()).hasError('date');
+ }
+
+ /*
+ * Whether the field is set to an invalid phone number.
+ */
+ public async hasPhoneFieldError(): Promise {
+ return (await this.#getFormError()).hasError('phone');
+ }
+
+ /*
+ * Whether the field is set to an invalid time.
+ */
+ public async hasTimeError(): Promise {
+ return (await this.#getFormError()).hasError('time');
+ }
+
/**
* Indicates whether the input box has disabled styles applied.
*/
From bda3d36eb01ff09f3783a747d17f634a488d421f Mon Sep 17 00:00:00 2001
From: Erika McVey <50454925+Blackbaud-ErikaMcVey@users.noreply.github.com>
Date: Mon, 5 Feb 2024 09:57:43 -0500
Subject: [PATCH 2/2] address PR feedback
---
.../forms/input-box/basic/demo.component.html | 11 ++--
.../input-box/basic/demo.component.spec.ts | 6 +-
.../forms/input-box/basic/demo.component.ts | 4 +-
.../forms/input-box/input-box.component.html | 8 ++-
libs/components/forms/src/index.ts | 5 +-
.../form-error/form-error.component.spec.ts | 46 ++++++++++++++
.../form-error/form-error.component.ts | 45 +++++++-------
.../modules/form-error/form-error.module.ts | 9 +++
.../form-error/form-errors-enabled-token.ts | 8 +++
.../modules/form-error/form-errors-token.ts | 3 -
.../form-error/form-errors.component.html | 62 +++++++++++--------
.../modules/form-error/form-errors.module.ts | 3 +-
.../input-box/input-box.component.html | 3 +-
.../modules/input-box/input-box.component.ts | 4 +-
.../lib/modules/input-box/input-box.module.ts | 10 +--
.../src/form-error/form-error-harness.ts | 2 +-
.../form-error/form-errors-harness.spec.ts | 8 +--
.../src/form-error/form-errors-harness.ts | 4 +-
.../input-box-harness-test.component.html | 2 +-
.../src/input-box/input-box-harness.ts | 2 +-
20 files changed, 158 insertions(+), 87 deletions(-)
create mode 100644 libs/components/forms/src/lib/modules/form-error/form-error.component.spec.ts
create mode 100644 libs/components/forms/src/lib/modules/form-error/form-error.module.ts
create mode 100644 libs/components/forms/src/lib/modules/form-error/form-errors-enabled-token.ts
delete mode 100644 libs/components/forms/src/lib/modules/form-error/form-errors-token.ts
diff --git a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.html b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.html
index 46842d9304..66fe5b1cdb 100644
--- a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.html
+++ b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.html
@@ -78,15 +78,14 @@ New member form
-
+
- Blur is not a color.
-
+ *ngIf="favoriteColor.errors?.['invalid']"
+ errorName="invalid"
+ errorText="Invalid Color is not a color"
+ />
diff --git a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.spec.ts
index a6690a2409..ba0cf2f438 100644
--- a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.spec.ts
@@ -88,7 +88,7 @@ describe('Basic input box demo', () => {
});
describe('favorite color field', () => {
- it('should not allow blur to be selected', async () => {
+ it('should not allow invalid color to be selected', async () => {
const harness = await setupTest({
dataSkyId: 'input-box-favorite-color',
});
@@ -97,10 +97,10 @@ describe('Basic input box demo', () => {
'.input-box-favorite-color-select',
) as HTMLSelectElement;
- selectEl.value = 'blur';
+ selectEl.value = 'invalid';
selectEl.dispatchEvent(new Event('change'));
- await expectAsync(harness.hasCustomFormError('color')).toBeResolvedTo(
+ await expectAsync(harness.hasCustomFormError('invalid')).toBeResolvedTo(
true,
);
});
diff --git a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.ts
index 17dbd1c267..3529ee4acc 100644
--- a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.ts
@@ -44,8 +44,8 @@ export class DemoComponent {
constructor() {
this.favoriteColor = new FormControl('none', [
(control): ValidationErrors | null => {
- if (control.value === 'blur') {
- return { color: true };
+ if (control.value === 'invalid') {
+ return { invalid: true };
}
return null;
diff --git a/apps/playground/src/app/components/forms/input-box/input-box.component.html b/apps/playground/src/app/components/forms/input-box/input-box.component.html
index 2a5f1e10e7..413f6eb57d 100644
--- a/apps/playground/src/app/components/forms/input-box/input-box.component.html
+++ b/apps/playground/src/app/components/forms/input-box/input-box.component.html
@@ -251,9 +251,11 @@
hintText="type in blue"
>
-
- Input must be blue.
-
+
+ `,
+})
+class FormErrorWithTokenComponent {}
+
+@Component({
+ standalone: true,
+ imports: [SkyFormErrorsModule, SkyFormErrorModule],
+ template: `
+
+ `,
+})
+class FormErrorWithoutTokenComponent {}
+
+describe('Form error component', () => {
+ it('renders an error message when form errors enabled token is provided', () => {
+ const fixture = TestBed.createComponent(FormErrorWithTokenComponent);
+
+ fixture.detectChanges();
+
+ expect(
+ fixture.nativeElement.querySelector('.sky-form-error'),
+ ).toBeVisible();
+ });
+
+ it('throws an error when form errors enabled token is not provided', () => {
+ expect(() =>
+ TestBed.createComponent(FormErrorWithoutTokenComponent),
+ ).toThrowError(
+ 'The `sky-form-error` component is not supported in the provided context.',
+ );
+ });
+});
diff --git a/libs/components/forms/src/lib/modules/form-error/form-error.component.ts b/libs/components/forms/src/lib/modules/form-error/form-error.component.ts
index 679f12e147..6773084805 100644
--- a/libs/components/forms/src/lib/modules/form-error/form-error.component.ts
+++ b/libs/components/forms/src/lib/modules/form-error/form-error.component.ts
@@ -1,18 +1,16 @@
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
- ChangeDetectorRef,
Component,
- HostBinding,
Input,
inject,
} from '@angular/core';
import { SkyStatusIndicatorModule } from '@skyux/indicators';
-import { FORM_ERRORS } from './form-errors-token';
+import { SKY_FORM_ERRORS_ENABLED } from './form-errors-enabled-token';
/**
- * @internal
+ * Displays default and custom input error messages for SKY UX form components.
*/
@Component({
selector: 'sky-form-error',
@@ -25,7 +23,7 @@ import { FORM_ERRORS } from './form-errors-token';
descriptionType="error"
indicatorType="danger"
>
-
+ {{ errorText }}
`,
styles: [
@@ -39,26 +37,27 @@ import { FORM_ERRORS } from './form-errors-token';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SkyFormErrorComponent {
- @Input()
- public set errorName(value: string) {
- this.#_errorName = value;
- this.#updateClasses();
- }
-
- public get errorName(): string {
- return this.#_errorName;
- }
-
- @HostBinding('class')
- protected cssClass = '';
+ /**
+ * The name of the error.
+ */
+ @Input({ required: true })
+ public errorName!: string;
- #_errorName = '';
+ /**
+ * The error message to display.
+ */
+ @Input({ required: true })
+ public errorText!: string;
- protected readonly formErrors = inject(FORM_ERRORS, { optional: true });
- readonly #changeDetector = inject(ChangeDetectorRef);
+ protected readonly formErrors = inject(SKY_FORM_ERRORS_ENABLED, {
+ optional: true,
+ });
- #updateClasses(): void {
- this.cssClass = `sky-form-error-${this.errorName} sky-form-error-indicator`;
- this.#changeDetector.markForCheck();
+ constructor() {
+ if (!this.formErrors) {
+ throw new Error(
+ 'The `sky-form-error` component is not supported in the provided context.',
+ );
+ }
}
}
diff --git a/libs/components/forms/src/lib/modules/form-error/form-error.module.ts b/libs/components/forms/src/lib/modules/form-error/form-error.module.ts
new file mode 100644
index 0000000000..0224e3456f
--- /dev/null
+++ b/libs/components/forms/src/lib/modules/form-error/form-error.module.ts
@@ -0,0 +1,9 @@
+import { NgModule } from '@angular/core';
+
+import { SkyFormErrorComponent } from './form-error.component';
+
+@NgModule({
+ imports: [SkyFormErrorComponent],
+ exports: [SkyFormErrorComponent],
+})
+export class SkyFormErrorModule {}
diff --git a/libs/components/forms/src/lib/modules/form-error/form-errors-enabled-token.ts b/libs/components/forms/src/lib/modules/form-error/form-errors-enabled-token.ts
new file mode 100644
index 0000000000..33ab025185
--- /dev/null
+++ b/libs/components/forms/src/lib/modules/form-error/form-errors-enabled-token.ts
@@ -0,0 +1,8 @@
+import { InjectionToken } from '@angular/core';
+
+/**
+ * @internal
+ */
+export const SKY_FORM_ERRORS_ENABLED = new InjectionToken(
+ 'SKY_FORM_ERRORS_ENABLED',
+);
diff --git a/libs/components/forms/src/lib/modules/form-error/form-errors-token.ts b/libs/components/forms/src/lib/modules/form-error/form-errors-token.ts
deleted file mode 100644
index 8cde04f9be..0000000000
--- a/libs/components/forms/src/lib/modules/form-error/form-errors-token.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { InjectionToken } from '@angular/core';
-
-export const FORM_ERRORS = new InjectionToken('FORM_ERRORS');
diff --git a/libs/components/forms/src/lib/modules/form-error/form-errors.component.html b/libs/components/forms/src/lib/modules/form-error/form-errors.component.html
index 850a0bda11..365cb38250 100644
--- a/libs/components/forms/src/lib/modules/form-error/form-errors.component.html
+++ b/libs/components/forms/src/lib/modules/form-error/form-errors.component.html
@@ -1,46 +1,56 @@
-
- {{ 'skyux_form_error_required' | skyLibResources : labelText }}
-
+
- {{
+ [errorText]="
'skyux_form_error_maxlength'
| skyLibResources : labelText : maxLengthError.requiredLength
- }}
-
+ "
+ />
- {{
+ [errorText]="
'skyux_form_error_minlength'
| skyLibResources : labelText : minLengthError.requiredLength
- }}
-
+ "
+ />
-
- {{ 'skyux_form_error_date' | skyLibResources }}
-
+
-
- {{ 'skyux_form_error_email' | skyLibResources }}
-
+
-
- {{ 'skyux_form_error_phone' | skyLibResources }}
-
+
-
- {{ 'skyux_form_error_time' | skyLibResources }}
-
+
-
- {{ 'skyux_form_error_url' | skyLibResources }}
-
+
diff --git a/libs/components/forms/src/lib/modules/form-error/form-errors.module.ts b/libs/components/forms/src/lib/modules/form-error/form-errors.module.ts
index 1f4ec9af83..8dcdd053c6 100644
--- a/libs/components/forms/src/lib/modules/form-error/form-errors.module.ts
+++ b/libs/components/forms/src/lib/modules/form-error/form-errors.module.ts
@@ -1,12 +1,13 @@
import { NgModule } from '@angular/core';
+import { SkyFormErrorComponent } from './form-error.component';
import { SkyFormErrorsComponent } from './form-errors.component';
/**
* @internal
*/
@NgModule({
- imports: [SkyFormErrorsComponent],
+ imports: [SkyFormErrorsComponent, SkyFormErrorComponent],
exports: [SkyFormErrorsComponent],
})
export class SkyFormErrorsModule {}
diff --git a/libs/components/forms/src/lib/modules/input-box/input-box.component.html b/libs/components/forms/src/lib/modules/input-box/input-box.component.html
index 633402a7d4..8f0f00991e 100644
--- a/libs/components/forms/src/lib/modules/input-box/input-box.component.html
+++ b/libs/components/forms/src/lib/modules/input-box/input-box.component.html
@@ -175,8 +175,7 @@
[labelText]="labelText"
[showErrors]="controlDir?.touched || controlDir?.dirty"
>
-
-
+
diff --git a/libs/components/forms/src/lib/modules/input-box/input-box.component.ts b/libs/components/forms/src/lib/modules/input-box/input-box.component.ts
index f0c47a4ca6..b0752d4391 100644
--- a/libs/components/forms/src/lib/modules/input-box/input-box.component.ts
+++ b/libs/components/forms/src/lib/modules/input-box/input-box.component.ts
@@ -31,7 +31,7 @@ import { SkyContentInfoProvider, SkyIdService } from '@skyux/core';
import { ReplaySubject } from 'rxjs';
-import { FORM_ERRORS } from '../form-error/form-errors-token';
+import { SKY_FORM_ERRORS_ENABLED } from '../form-error/form-errors-enabled-token';
import { SkyInputBoxAdapterService } from './input-box-adapter.service';
import { SkyInputBoxControlDirective } from './input-box-control.directive';
@@ -50,7 +50,7 @@ import { SkyInputBoxPopulateArgs } from './input-box-populate-args';
SkyInputBoxAdapterService,
SkyInputBoxHostService,
{
- provide: FORM_ERRORS,
+ provide: SKY_FORM_ERRORS_ENABLED,
useValue: true,
},
],
diff --git a/libs/components/forms/src/lib/modules/input-box/input-box.module.ts b/libs/components/forms/src/lib/modules/input-box/input-box.module.ts
index 768718ea68..60735b390d 100644
--- a/libs/components/forms/src/lib/modules/input-box/input-box.module.ts
+++ b/libs/components/forms/src/lib/modules/input-box/input-box.module.ts
@@ -3,8 +3,8 @@ import { NgModule } from '@angular/core';
import { SkyThemeModule } from '@skyux/theme';
import { SkyCharacterCounterModule } from '../character-counter/character-counter.module';
-import { SkyFormErrorComponent } from '../form-error/form-error.component';
-import { SkyFormErrorsComponent } from '../form-error/form-errors.component';
+import { SkyFormErrorModule } from '../form-error/form-error.module';
+import { SkyFormErrorsModule } from '../form-error/form-errors.module';
import { SkyInputBoxControlDirective } from './input-box-control.directive';
import { SkyInputBoxHelpInlineComponent } from './input-box-help-inline.component';
@@ -15,8 +15,8 @@ import { SkyInputBoxComponent } from './input-box.component';
imports: [
CommonModule,
SkyCharacterCounterModule,
- SkyFormErrorComponent,
- SkyFormErrorsComponent,
+ SkyFormErrorsModule,
+ SkyFormErrorModule,
SkyInputBoxControlDirective,
SkyInputBoxHelpInlineComponent,
SkyThemeModule,
@@ -24,7 +24,7 @@ import { SkyInputBoxComponent } from './input-box.component';
exports: [
SkyInputBoxComponent,
SkyInputBoxControlDirective,
- SkyFormErrorComponent,
+ SkyFormErrorModule,
],
})
export class SkyInputBoxModule {}
diff --git a/libs/components/forms/testing/src/form-error/form-error-harness.ts b/libs/components/forms/testing/src/form-error/form-error-harness.ts
index 7dcada5095..bcbd4d1a8a 100644
--- a/libs/components/forms/testing/src/form-error/form-error-harness.ts
+++ b/libs/components/forms/testing/src/form-error/form-error-harness.ts
@@ -19,7 +19,7 @@ export class SkyFormErrorHarness extends SkyComponentHarness {
return SkyFormErrorHarness.getDataSkyIdPredicate(filters);
}
- /*
+ /**
* Gets the error name.
*/
public async getErrorName(): Promise {
diff --git a/libs/components/forms/testing/src/form-error/form-errors-harness.spec.ts b/libs/components/forms/testing/src/form-error/form-errors-harness.spec.ts
index a7692d5c44..48353abe3f 100644
--- a/libs/components/forms/testing/src/form-error/form-errors-harness.spec.ts
+++ b/libs/components/forms/testing/src/form-error/form-errors-harness.spec.ts
@@ -5,8 +5,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ValidationErrors } from '@angular/forms';
// eslint-disable-next-line @nx/enforce-module-boundaries
import {
- FORM_ERRORS,
- SkyFormErrorComponent,
+ SKY_FORM_ERRORS_ENABLED,
+ SkyFormErrorModule,
SkyFormErrorsModule,
} from '@skyux/forms';
@@ -17,7 +17,7 @@ import { SkyFormErrorsHarness } from './form-errors-harness';
selector: 'sky-form-errors-test',
providers: [
{
- provide: FORM_ERRORS,
+ provide: SKY_FORM_ERRORS_ENABLED,
useValue: true,
},
],
@@ -43,7 +43,7 @@ describe('Form errors harness', () => {
}> {
await TestBed.configureTestingModule({
declarations: [TestComponent],
- imports: [SkyFormErrorsModule, SkyFormErrorComponent],
+ imports: [SkyFormErrorsModule, SkyFormErrorModule],
}).compileComponents();
const fixture = TestBed.createComponent(TestComponent);
diff --git a/libs/components/forms/testing/src/form-error/form-errors-harness.ts b/libs/components/forms/testing/src/form-error/form-errors-harness.ts
index c72fe0a17e..6411541317 100644
--- a/libs/components/forms/testing/src/form-error/form-errors-harness.ts
+++ b/libs/components/forms/testing/src/form-error/form-errors-harness.ts
@@ -21,7 +21,7 @@ export class SkyFormErrorsHarness extends SkyComponentHarness {
}
/**
- * Gets an array of all errors fired.
+ * Gets a list of all errors fired.
*/
public async getFormErrors(): Promise<{ errorName: string | null }[]> {
const formErrorHarnesses = await this.locatorForAll(
@@ -40,7 +40,7 @@ export class SkyFormErrorsHarness extends SkyComponentHarness {
*/
public async hasError(errorName: string): Promise {
const formErrors = await this.getFormErrors();
- return !!formErrors.find((error) => {
+ return formErrors.some((error) => {
return error.errorName === errorName;
});
}
diff --git a/libs/components/forms/testing/src/input-box/fixtures/input-box-harness-test.component.html b/libs/components/forms/testing/src/input-box/fixtures/input-box-harness-test.component.html
index e8e5bf744e..a1fff97fec 100644
--- a/libs/components/forms/testing/src/input-box/fixtures/input-box-harness-test.component.html
+++ b/libs/components/forms/testing/src/input-box/fixtures/input-box-harness-test.component.html
@@ -85,7 +85,7 @@
labelText="easyModeCustomError"
>
- This is a customer error
+
diff --git a/libs/components/forms/testing/src/input-box/input-box-harness.ts b/libs/components/forms/testing/src/input-box/input-box-harness.ts
index dc00ac57c8..9288756e5f 100644
--- a/libs/components/forms/testing/src/input-box/input-box-harness.ts
+++ b/libs/components/forms/testing/src/input-box/input-box-harness.ts
@@ -9,7 +9,7 @@ import { SkyStatusIndicatorHarness } from '@skyux/indicators/testing';
import { SkyPopoverHarness } from '@skyux/popovers/testing';
import { SkyCharacterCounterIndicatorHarness } from '../character-counter/character-counter-indicator-harness';
-import { SkyFormErrorsHarness } from '../public-api';
+import { SkyFormErrorsHarness } from '../form-error/form-errors-harness';
import { SkyInputBoxHarnessFilters } from './input-box-harness-filters';