diff --git a/apps/code-examples/src/app/code-examples/forms/checkbox/basic/demo.component.html b/apps/code-examples/src/app/code-examples/forms/checkbox/basic/demo.component.html
index d75615aa1e..da210ed334 100644
--- a/apps/code-examples/src/app/code-examples/forms/checkbox/basic/demo.component.html
+++ b/apps/code-examples/src/app/code-examples/forms/checkbox/basic/demo.component.html
@@ -13,5 +13,12 @@
+
+
+
diff --git a/apps/code-examples/src/app/code-examples/forms/checkbox/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/forms/checkbox/basic/demo.component.ts
index 47a8452783..bb0471a9c5 100644
--- a/apps/code-examples/src/app/code-examples/forms/checkbox/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/forms/checkbox/basic/demo.component.ts
@@ -22,10 +22,13 @@ export class DemoComponent {
email: new FormControl(false),
phone: new FormControl(false),
text: new FormControl(false),
+ terms: new FormControl(false),
});
}
protected onSubmit(): void {
+ this.formGroup.markAllAsTouched();
+
console.log(this.formGroup.value);
}
}
diff --git a/apps/e2e/forms-storybook-e2e/src/e2e/checkbox.component.cy.ts b/apps/e2e/forms-storybook-e2e/src/e2e/checkbox.component.cy.ts
index dc9709ca2b..4f07c25794 100644
--- a/apps/e2e/forms-storybook-e2e/src/e2e/checkbox.component.cy.ts
+++ b/apps/e2e/forms-storybook-e2e/src/e2e/checkbox.component.cy.ts
@@ -13,6 +13,8 @@ describe('forms-storybook - checkbox', () => {
cy.get('app-checkbox')
.should('exist')
.should('be.visible')
+ .get('#touched-required-checkbox')
+ .dblclick()
.get('#standard-checkboxes')
.should('exist')
.should('be.visible')
diff --git a/apps/e2e/forms-storybook/src/app/checkbox/checkbox.component.html b/apps/e2e/forms-storybook/src/app/checkbox/checkbox.component.html
index 608834aa42..b319ad2437 100644
--- a/apps/e2e/forms-storybook/src/app/checkbox/checkbox.component.html
+++ b/apps/e2e/forms-storybook/src/app/checkbox/checkbox.component.html
@@ -48,6 +48,17 @@
+
+
+
+
+
diff --git a/apps/e2e/forms-storybook/src/app/checkbox/checkbox.module.ts b/apps/e2e/forms-storybook/src/app/checkbox/checkbox.module.ts
index d6dd5a7b69..6758965448 100644
--- a/apps/e2e/forms-storybook/src/app/checkbox/checkbox.module.ts
+++ b/apps/e2e/forms-storybook/src/app/checkbox/checkbox.module.ts
@@ -1,5 +1,6 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { SkyCheckboxModule } from '@skyux/forms';
import { SkyHelpInlineModule } from '@skyux/indicators';
@@ -13,6 +14,7 @@ const routes: Routes = [{ path: '', component: CheckboxComponent }];
declarations: [CheckboxComponent],
imports: [
CommonModule,
+ FormsModule,
SkyCheckboxModule,
SkyFluidGridModule,
SkyHelpInlineModule,
diff --git a/apps/playground/src/app/components/forms/checkbox/checkbox.component.ts b/apps/playground/src/app/components/forms/checkbox/checkbox.component.ts
index ce44f26b4f..16339e6270 100644
--- a/apps/playground/src/app/components/forms/checkbox/checkbox.component.ts
+++ b/apps/playground/src/app/components/forms/checkbox/checkbox.component.ts
@@ -1,5 +1,9 @@
import { Component, OnInit } from '@angular/core';
-import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
+import {
+ FormControl,
+ UntypedFormBuilder,
+ UntypedFormGroup,
+} from '@angular/forms';
@Component({
selector: 'app-checkbox',
@@ -28,7 +32,7 @@ export class CheckboxComponent implements OnInit {
public ngOnInit(): void {
this.reactiveFormGroup = this.#formBuilder.group({
- reactiveCheckbox: [undefined],
+ reactiveCheckbox: new FormControl(undefined),
});
}
diff --git a/libs/components/forms/src/assets/locales/resources_en_US.json b/libs/components/forms/src/assets/locales/resources_en_US.json
index f581bd7466..2edd7af960 100644
--- a/libs/components/forms/src/assets/locales/resources_en_US.json
+++ b/libs/components/forms/src/assets/locales/resources_en_US.json
@@ -150,5 +150,9 @@
"skyux_input_box_help_inline_aria_label": {
"_description": "The accessible label for an input box help inline button",
"message": "Show help content for {0}"
+ },
+ "skyux_checkbox_required_label_text": {
+ "_description": "The label text portion of the required validation message",
+ "message": "This selection"
}
}
diff --git a/libs/components/forms/src/lib/modules/checkbox/checkbox.component.html b/libs/components/forms/src/lib/modules/checkbox/checkbox.component.html
index 0edf694ea9..a0e041fc58 100644
--- a/libs/components/forms/src/lib/modules/checkbox/checkbox.component.html
+++ b/libs/components/forms/src/lib/modules/checkbox/checkbox.component.html
@@ -17,6 +17,10 @@
[attr.aria-label]="label"
[attr.aria-labelledby]="labelledBy"
[attr.aria-required]="required ? true : null"
+ [attr.aria-invalid]="!!ngControl?.errors"
+ [attr.aria-errormessage]="
+ labelText && ngControl?.errors ? errorId : undefined
+ "
(blur)="onInputBlur()"
(change)="onInteractionEvent($event)"
#inputEl
@@ -58,3 +62,12 @@
+
+
+
diff --git a/libs/components/forms/src/lib/modules/checkbox/checkbox.component.ts b/libs/components/forms/src/lib/modules/checkbox/checkbox.component.ts
index 3faa747fb9..829bc189b8 100644
--- a/libs/components/forms/src/lib/modules/checkbox/checkbox.component.ts
+++ b/libs/components/forms/src/lib/modules/checkbox/checkbox.component.ts
@@ -14,6 +14,7 @@ import { SkyIdService, SkyLogService } from '@skyux/core';
import { BehaviorSubject, Observable } from 'rxjs';
+import { SKY_FORM_ERRORS_ENABLED } from '../form-error/form-errors-enabled-token';
import { SkyFormsUtility } from '../shared/forms-utility';
import { SkyCheckboxChange } from './checkbox-change';
@@ -26,6 +27,7 @@ import { SkyCheckboxChange } from './checkbox-change';
selector: 'sky-checkbox',
templateUrl: './checkbox.component.html',
styleUrls: ['./checkbox.component.scss'],
+ providers: [{ provide: SKY_FORM_ERRORS_ENABLED, useValue: true }],
})
export class SkyCheckboxComponent implements ControlValueAccessor, OnInit {
/**
@@ -260,13 +262,19 @@ export class SkyCheckboxComponent implements ControlValueAccessor, OnInit {
#_required = false;
#changeDetector = inject(ChangeDetectorRef);
- #defaultId = inject(SkyIdService).generateId();
+ #idSvc = inject(SkyIdService);
+ #defaultId = this.#idSvc.generateId();
#logger = inject(SkyLogService);
- #ngControl = inject(NgControl, { optional: true, self: true });
+
+ protected readonly ngControl = inject(NgControl, {
+ optional: true,
+ self: true,
+ });
+ protected readonly errorId = this.#idSvc.generateId();
constructor() {
- if (this.#ngControl) {
- this.#ngControl.valueAccessor = this;
+ if (this.ngControl) {
+ this.ngControl.valueAccessor = this;
}
this.#checkedChange = new BehaviorSubject(this.checked);
@@ -282,10 +290,10 @@ export class SkyCheckboxComponent implements ControlValueAccessor, OnInit {
}
public ngOnInit(): void {
- if (this.#ngControl) {
+ if (this.ngControl) {
// Backwards compatibility support for anyone still using Validators.Required.
this.required =
- this.required || SkyFormsUtility.hasRequiredValidation(this.#ngControl);
+ this.required || SkyFormsUtility.hasRequiredValidation(this.ngControl);
}
}
@@ -361,16 +369,16 @@ export class SkyCheckboxComponent implements ControlValueAccessor, OnInit {
#setValidators(): void {
if (
this.required &&
- !this.#ngControl?.control?.hasValidator(Validators.requiredTrue)
+ !this.ngControl?.control?.hasValidator(Validators.requiredTrue)
) {
- this.#ngControl?.control?.addValidators(Validators.requiredTrue);
- this.#ngControl?.control?.updateValueAndValidity();
+ this.ngControl?.control?.addValidators(Validators.requiredTrue);
+ this.ngControl?.control?.updateValueAndValidity();
} else if (
!this.required &&
- this.#ngControl?.control?.hasValidator(Validators.requiredTrue)
+ this.ngControl?.control?.hasValidator(Validators.requiredTrue)
) {
- this.#ngControl.control.removeValidators(Validators.requiredTrue);
- this.#ngControl.control?.updateValueAndValidity();
+ this.ngControl.control.removeValidators(Validators.requiredTrue);
+ this.ngControl.control?.updateValueAndValidity();
}
}
}
diff --git a/libs/components/forms/src/lib/modules/checkbox/checkbox.module.ts b/libs/components/forms/src/lib/modules/checkbox/checkbox.module.ts
index 9eef50c8f1..9d98a73d8e 100644
--- a/libs/components/forms/src/lib/modules/checkbox/checkbox.module.ts
+++ b/libs/components/forms/src/lib/modules/checkbox/checkbox.module.ts
@@ -4,12 +4,28 @@ import { FormsModule } from '@angular/forms';
import { SkyTrimModule } from '@skyux/core';
import { SkyIconModule } from '@skyux/indicators';
+import { SkyFormErrorModule } from '../form-error/form-error.module';
+import { SkyFormErrorsModule } from '../form-error/form-errors.module';
+import { SkyFormsResourcesModule } from '../shared/sky-forms-resources.module';
+
import { SkyCheckboxLabelComponent } from './checkbox-label.component';
import { SkyCheckboxComponent } from './checkbox.component';
@NgModule({
declarations: [SkyCheckboxComponent, SkyCheckboxLabelComponent],
- imports: [CommonModule, FormsModule, SkyIconModule, SkyTrimModule],
- exports: [SkyCheckboxComponent, SkyCheckboxLabelComponent],
+ imports: [
+ CommonModule,
+ FormsModule,
+ SkyFormErrorModule,
+ SkyFormErrorsModule,
+ SkyFormsResourcesModule,
+ SkyIconModule,
+ SkyTrimModule,
+ ],
+ exports: [
+ SkyCheckboxComponent,
+ SkyCheckboxLabelComponent,
+ SkyFormErrorModule,
+ ],
})
export class SkyCheckboxModule {}
diff --git a/libs/components/forms/src/lib/modules/shared/sky-forms-resources.module.ts b/libs/components/forms/src/lib/modules/shared/sky-forms-resources.module.ts
index 3dd8c03b1b..19c6992432 100644
--- a/libs/components/forms/src/lib/modules/shared/sky-forms-resources.module.ts
+++ b/libs/components/forms/src/lib/modules/shared/sky-forms-resources.module.ts
@@ -96,6 +96,7 @@ const RESOURCES: { [locale: string]: SkyLibResources } = {
skyux_input_box_help_inline_aria_label: {
message: 'Show help content for {0}',
},
+ skyux_checkbox_required_label_text: { message: 'This selection' },
},
};
diff --git a/libs/components/forms/testing/src/checkbox/checkbox-harness.spec.ts b/libs/components/forms/testing/src/checkbox/checkbox-harness.spec.ts
index abf0c51218..6bf50da55d 100644
--- a/libs/components/forms/testing/src/checkbox/checkbox-harness.spec.ts
+++ b/libs/components/forms/testing/src/checkbox/checkbox-harness.spec.ts
@@ -107,6 +107,15 @@ describe('Checkbox harness', () => {
await expectAsync(checkboxHarness.getLabelText()).toBeResolvedTo(undefined);
});
+ it('should get the label when specified via labelText input', async () => {
+ const { checkboxHarness } = await setupTest({
+ dataSkyId: 'my-phone-checkbox',
+ hideEmailLabel: true,
+ });
+
+ await expectAsync(checkboxHarness.getLabelText()).toBeResolvedTo('Phone');
+ });
+
it('should get the checkbox name and value', async () => {
const { checkboxHarness } = await setupTest({
dataSkyId: 'my-email-checkbox',
@@ -131,4 +140,39 @@ describe('Checkbox harness', () => {
'Could not toggle the checkbox because it is disabled.',
);
});
+
+ it('should display a required error message when there is an error', async () => {
+ const { checkboxHarness } = await setupTest({
+ dataSkyId: 'my-phone-checkbox',
+ });
+
+ await checkboxHarness.check();
+ await checkboxHarness.uncheck();
+
+ await expectAsync(checkboxHarness.hasRequiredError()).toBeResolvedTo(true);
+ });
+
+ it('should display a custom error message when there is a custom validation error', async () => {
+ const { checkboxHarness } = await setupTest({
+ dataSkyId: 'my-mail-checkbox',
+ });
+
+ await checkboxHarness.check();
+ await checkboxHarness.uncheck();
+ await checkboxHarness.check();
+
+ await expectAsync(
+ checkboxHarness.hasCustomError('requiredFalse'),
+ ).toBeResolvedTo(true);
+ });
+
+ it('should throw an error if no form error is found', async () => {
+ const { checkboxHarness } = await setupTest({
+ dataSkyId: 'my-email-checkbox',
+ });
+
+ await expectAsync(checkboxHarness.hasRequiredError()).toBeRejectedWithError(
+ 'No form errors found.',
+ );
+ });
});
diff --git a/libs/components/forms/testing/src/checkbox/checkbox-harness.ts b/libs/components/forms/testing/src/checkbox/checkbox-harness.ts
index 6f9eb6fd02..e356204035 100644
--- a/libs/components/forms/testing/src/checkbox/checkbox-harness.ts
+++ b/libs/components/forms/testing/src/checkbox/checkbox-harness.ts
@@ -1,6 +1,8 @@
import { HarnessPredicate } from '@angular/cdk/testing';
import { SkyComponentHarness } from '@skyux/core/testing';
+import { SkyFormErrorsHarness } from '../form-error/form-errors-harness';
+
import { SkyCheckboxHarnessFilters } from './checkbox-harness-filters';
import { SkyCheckboxLabelHarness } from './checkbox-label-harness';
@@ -18,6 +20,16 @@ export class SkyCheckboxHarness extends SkyComponentHarness {
#getLabel = this.locatorForOptional(SkyCheckboxLabelHarness);
+ async #getFormErrors(): Promise {
+ const harness = await this.locatorForOptional(SkyFormErrorsHarness)();
+
+ if (harness) {
+ return harness;
+ }
+
+ throw Error('No form errors found.');
+ }
+
/**
* Gets a `HarnessPredicate` that can be used to search for a
* `SkyCheckboxHarness` that meets certain criteria.
@@ -129,6 +141,14 @@ export class SkyCheckboxHarness extends SkyComponentHarness {
}
}
+ public async hasRequiredError(): Promise {
+ return (await this.#getFormErrors()).hasError('required');
+ }
+
+ public async hasCustomError(errorName: string): Promise {
+ return (await this.#getFormErrors()).hasError(errorName);
+ }
+
async #toggle(): Promise {
if (await this.isDisabled()) {
throw new Error('Could not toggle the checkbox because it is disabled.');
diff --git a/libs/components/forms/testing/src/checkbox/fixtures/checkbox-harness-test.component.html b/libs/components/forms/testing/src/checkbox/fixtures/checkbox-harness-test.component.html
index 889ad13aff..bebc14e57b 100644
--- a/libs/components/forms/testing/src/checkbox/fixtures/checkbox-harness-test.component.html
+++ b/libs/components/forms/testing/src/checkbox/fixtures/checkbox-harness-test.component.html
@@ -15,12 +15,25 @@
+
+
+
- Phone
+
diff --git a/libs/components/forms/testing/src/checkbox/fixtures/checkbox-harness-test.component.ts b/libs/components/forms/testing/src/checkbox/fixtures/checkbox-harness-test.component.ts
index 15e3741616..b8840529c9 100644
--- a/libs/components/forms/testing/src/checkbox/fixtures/checkbox-harness-test.component.ts
+++ b/libs/components/forms/testing/src/checkbox/fixtures/checkbox-harness-test.component.ts
@@ -1,9 +1,10 @@
import { Component } from '@angular/core';
import {
+ AbstractControl,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
- Validators,
+ ValidationErrors,
} from '@angular/forms';
@Component({
@@ -13,15 +14,27 @@ import {
export class CheckboxHarnessTestComponent {
public myForm: UntypedFormGroup;
public hideEmailLabel = false;
+ public mailControl: UntypedFormControl;
#formBuilder: UntypedFormBuilder;
constructor(formBuilder: UntypedFormBuilder) {
this.#formBuilder = formBuilder;
+ this.mailControl = new UntypedFormControl(false, [
+ (control: AbstractControl): ValidationErrors | null => {
+ if (control.value) {
+ return { requiredFalse: true };
+ }
+
+ return null;
+ },
+ ]);
+
this.myForm = this.#formBuilder.group({
email: new UntypedFormControl(false),
- phone: new UntypedFormControl(false, [Validators.required]),
+ phone: new UntypedFormControl(false),
+ mail: this.mailControl,
});
}