checkbox-selection-input
v15.0.5
Published
This is an Angular Module containing Components/Services using Material
Downloads
4
Readme
Checkbox Selection Input Component
Overview
The checkbox-selection-input library provides a comprehensive Material Design checkbox component that works seamlessly with Angular forms. It supports multiple selection with validation limits (min/max), disabled states, and both string and object data formats. The component implements Angular's ControlValueAccessor and NG_VALIDATORS interfaces for seamless form integration and validation.
Core Capabilities
☑️ Advanced Checkbox Selection Interface
- Multiple Selection: Support for selecting multiple checkboxes simultaneously
- Form Validation: Built-in min/max selection validation with error handling
- Flexible Data Support: Accepts both string arrays and complex object arrays
- State Management: Disabled states, pre-selected items, and dynamic enabling/disabling
- Material Design: Built on Angular Material checkbox foundation
- ControlValueAccessor Integration: Full Angular form control support
- Validation Integration: Native Angular validation system compatibility
- Dynamic Control: Runtime data updates and validation constraint changes
🔧 Features
✅ ControlValueAccessor Implementation - Works with Angular forms
✅ NG_VALIDATORS Integration - Native validation support
✅ Material Design Integration - Uses Angular Material components
✅ Multiple Selection - Select multiple items with checkboxes
✅ Min/Max Validation selection limits
✅ - Configurable Disable Max Behavior - Auto-disable checkboxes when max reached
✅ Flexible Data Types - Support strings and objects
✅ Pre-selection - Initialize with selected items
✅ Disabled Items - Mark individual items as non-selectable
✅ Change Detection - Console logging for debugging
✅ Form Integration - Reactive and template-driven forms
Key Benefits
| Feature | Description | |---------|-------------| | Flexible Selection | Support for multiple item selection with validation | | Rich Validation | Built-in min/max selection limits with error states | | Data Format Support | Works with simple strings or complex objects | | State Management | Disabled states and pre-selection capabilities | | Form Integration | Seamless Angular form control and validation integration | | Material Design | Consistent Material Design styling and behavior |
Demo Component (CheckboxSelectionDemoComponent)
The demo component showcases 6 different checkbox configurations demonstrating various use cases and validation scenarios.
Usage
To use the demo component in your application:
<app-checkbox-selection-demo></app-checkbox-selection-demo>Demo Configurations
The demo includes 6 different checkbox setups:
Demo 1: Basic Configuration
- No label/placeholder
- Not required
- Basic form control integration
Demo 2: Labeled Configuration
- With label and placeholder
- Required validation
- Error display toggle
Demo 3: Minimum Selection
- Label: "Providers"
- Minimum 2 selections required
- Error handling for insufficient selections
Demo 4: Maximum Selection
- Label: "Providers"
- Maximum 2 selections allowed
- Error handling for exceeding limit
Demo 5: Auto-disable at Max
- Label: "Providers"
- Maximum 2 selections
- Auto-disable remaining checkboxes when max reached
Demo 6: Combined Min/Max
- Label: "Providers"
- Minimum 1, Maximum 2 selections
- Auto-disable at max
- Complex validation scenarios
Demo Features
- Data Type Toggle: Switch between string arrays and object arrays
- Patch Testing: Programmatically set values for testing
- Enable/Disable Controls: Test form control state changes
- Change Detection: Console logging of value changes
- Error Display: Toggle error message visibility
- Reset Functionality: Clear all selections
Summary
The checkbox-selection-input library provides a flexible, Material Design-compliant checkbox component with comprehensive form integration, validation support, and multiple selection capabilities for Angular applications.
Quick Start Guide
Installation & Setup (2 minutes)
1. Import Module
// app.module.ts
import { CheckboxSelectionInputModule } from 'checkbox-selection-input';
@NgModule({
imports: [
CheckboxSelectionInputModule
]
})
export class AppModule { }2. No Module Configuration Required
The CheckboxSelectionInputModule does not require global configuration. Components can be used immediately after module import.
Quick Examples
Example 1: Basic Multiple Selection
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-basic-checkbox',
template: `
<app-checkbox-selection-input
[formControl]="selectionControl"
[data]="options">
</app-checkbox-selection-input>
<div>Selected: {{ selectionControl.value | json }}</div>
`
})
export class BasicCheckboxComponent {
selectionControl = new FormControl();
options = ['Option 1', 'Option 2', 'Option 3', 'Option 4'];
}Example 2: Required Selection with Validation
import { Component } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-required-checkbox',
template: `
<app-checkbox-selection-input
[formControl]="requiredControl"
[data]="providers"
label="Select Providers"
placeholder="Choose your providers"
[minSelection]="2"
[maxSelection]="3">
</app-checkbox-selection-input>
<div class="errors" *ngIf="requiredControl.errors">
<div *ngIf="requiredControl.hasError('minRequired')">
Please select at least 2 providers
</div>
<div *ngIf="requiredControl.hasError('maxExceeded')">
You can select maximum 3 providers
</div>
<div *ngIf="requiredControl.hasError('required')">
Provider selection is required
</div>
</div>
`
})
export class RequiredCheckboxComponent {
requiredControl = new FormControl([], [
Validators.required,
Validators.minLength(2),
Validators.maxLength(3)
]);
providers = ['Telus', 'AT&T', 'Bell', 'Rogers', 'Verizon'];
}Example 3: Object Data with Disabled Items
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { SelectionItem } from 'checkbox-selection-input';
@Component({
selector: 'app-object-checkbox',
template: `
<app-checkbox-selection-input
[formControl]="objectControl"
[data]="userOptions"
[disableMax]="true">
</app-checkbox-selection-input>
<div>Selected Users: {{ objectControl.value | json }}</div>
`
})
export class ObjectCheckboxComponent {
objectControl = new FormControl();
userOptions = [
{ id: 1, value: 'John Doe', selected: true },
{ id: 2, value: 'Jane Smith', disabled: true },
{ id: 3, value: 'Bob Johnson', selected: true },
{ id: 4, value: 'Alice Brown' }
];
}Example 4: Auto-disable at Maximum
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-auto-disable-checkbox',
template: `
<app-checkbox-selection-input
[formControl]="featuresControl"
[data]="features"
label="Select Features"
[maxSelection]="2"
[disableMax]="true">
</app-checkbox-selection-input>
<div class="info">
Selected: {{ featuresControl.value?.length || 0 }} / 2 maximum
</div>
`
})
export class AutoDisableCheckboxComponent {
featuresControl = new FormControl();
features = [
'Dark Mode',
'Notifications',
'Analytics',
'Export Data',
'API Access',
'Advanced Filters'
];
}Example 5: Dynamic Data with Form Validation
import { Component } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { SelectionItem } from 'checkbox-selection-input';
@Component({
selector: 'app-dynamic-checkbox',
template: `
<form [formGroup]="dynamicForm">
<app-checkbox-selection-input
formControlName="permissions"
[data]="permissionOptions"
label="User Permissions"
[minSelection]="1"
[maxSelection]="4"
[disableMax]="true">
</app-checkbox-selection-input>
</form>
<div class="form-status">
<div>Valid: {{ dynamicForm.get('permissions')?.valid }}</div>
<div>Touched: {{ dynamicForm.get('permissions')?.touched }}</div>
<div>Selected Count: {{ dynamicForm.get('permissions')?.value?.length || 0 }}</div>
</div>
<div class="controls">
<button (click)="addPermission()">Add Permission</button>
<button (click)="removeLastPermission()">Remove Last</button>
<button (click)="resetForm()">Reset</button>
</div>
`,
styles: [`
.controls { margin-top: 1rem; display: flex; gap: 0.5rem; }
.form-status { margin-top: 1rem; padding: 0.5rem; background: #f5f5f5; }
`]
})
export class DynamicCheckboxComponent {
constructor(private fb: FormBuilder) {}
dynamicForm = this.fb.group({
permissions: [[], [Validators.required, Validators.minLength(1), Validators.maxLength(4)]]
});
permissionOptions = [
{ id: 1, value: 'Read Files' },
{ id: 2, value: 'Write Files' },
{ id: 3, value: 'Delete Files' },
{ id: 4, value: 'Share Files' },
{ id: 5, value: 'Admin Access' }
];
addPermission() {
const availablePermissions = this.permissionOptions.filter(
p => !this.dynamicForm.get('permissions')?.value?.includes(p.value)
);
if (availablePermissions.length > 0) {
const current = this.dynamicForm.get('permissions')?.value || [];
this.dynamicForm.get('permissions')?.setValue([...current, availablePermissions[0].value]);
}
}
removeLastPermission() {
const current = this.dynamicForm.get('permissions')?.value || [];
if (current.length > 0) {
this.dynamicForm.get('permissions')?.setValue(current.slice(0, -1));
}
}
resetForm() {
this.dynamicForm.get('permissions')?.reset();
}
}Component API
Inputs
| Input | Type | Description | Default |
| :--- | :--- | :--- | :--- |
| data | any[] \| string[] | Array of items or strings defining checkbox options | (Required) |
| label | string | Optional label text for the checkbox group | undefined |
| placeholder | string | Optional placeholder text | undefined |
| error | string | Optional error message to display | undefined |
| disableMax | boolean | If true, disables checkboxes when max selection is reached | false |
| useDefaultReset | boolean | Use default reset behavior | false |
| minSelection | number | Minimum number of selections required | 0 |
| maxSelection | number | Maximum number of selections allowed | 0 |
Outputs
| Output | Type | Description |
| :--- | :--- | :--- |
| selectionChange | EventEmitter<string[]> | Emits array of selected values when selection changes |
Form Control Integration
The component works with Angular form controls and emits:
- Single array value: Array of selected values (strings or objects based on input data)
- Validation state: Integrates with Angular's validation system
- Touch/dirty states: Properly tracks form control states
Model Structures
SelectionItem Interface
export interface SelectionItemInterface {
id: number | string; // Unique identifier for the item
value: string; // The value to be selected/returned
disabled?: boolean; // Whether this item is disabled
selected?: boolean; // Whether this item is pre-selected
}SelectionItem Class
export class SelectionItem implements SelectionItemInterface {
constructor(
public id = crypto.randomUUID(), // Auto-generates UUID if not provided
public value = '',
public disabled?: boolean = false,
public selected?: boolean = false,
) {}
static adapt(item?: any): SelectionItem {
return new SelectionItem(
item?.id, // Use provided ID or undefined
(item?.value) ? item.value : item, // Use value or fallback to item itself
(item?.disabled) ? true : false, // Convert to boolean
(item?.selected) ? true : false, // Convert to boolean
);
}
}Usage Examples
// String array data (automatically converted to SelectionItem)
const stringData = ['Option 1', 'Option 2', 'Option 3'];
// Object array data
const objectData = [
{ id: 1, value: 'Telus', selected: true },
{ id: 2, value: 'AT&T', disabled: true },
{ id: 3, value: 'Bell' }
];
// Manual SelectionItem creation
const manualItems = [
new SelectionItem('1', 'Option 1', false, true),
new SelectionItem('2', 'Option 2', true, false),
new SelectionItem('3', 'Option 3', false, false)
];
// Using adapt method for flexible data
const adaptedItems = [
SelectionItem.adapt({ value: 'Item 1', selected: true }),
SelectionItem.adapt({ id: 'custom-id', value: 'Item 2', disabled: true }),
SelectionItem.adapt('Item 3') // String fallback
];Form Integration
ControlValueAccessor Implementation
The component implements Angular's ControlValueAccessor interface:
// writeValue(value: string[]): void
// Sets the value of the control (array of selected values)
writeValue(value: string[]): void {
// Handle incoming form control value
// Update component state and checkbox states
}
// registerOnChange(fn: any): void
// Registers a callback for value changes
registerOnChange(fn: any): void {
this.onChange = fn;
}
// registerOnTouched(fn: any): void
// Registers a callback for touch events
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
// setDisabledState(isDisabled: boolean): void
// Sets disabled state for entire component
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.selectionControl.disable();
} else {
this.selectionControl.enable();
}
}Validation Integration
The component also implements NG_VALIDATORS:
// validate(control: AbstractControl): ValidationErrors | null
// Performs validation and returns errors if any
validate(control: AbstractControl): ValidationErrors | null {
// Check min/max selection requirements
// Return validation errors or null
return { minRequired: true, maxExceeded: false };
}Form Integration Examples
Reactive Forms
import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-reactive-checkbox',
template: `
<form [formGroup]="checkboxForm">
<app-checkbox-selection-input
formControlName="selections"
[data]="options"
label="Select Options"
[minSelection]="2"
[maxSelection]="4"
[disableMax]="true">
</app-checkbox-selection-input>
<div class="errors" *ngIf="checkboxForm.get('selections')?.errors">
<div *ngIf="checkboxForm.get('selections')?.hasError('minRequired')">
Please select at least 2 options
</div>
<div *ngIf="checkboxForm.get('selections')?.hasError('maxExceeded')">
Maximum selections exceeded
</div>
</div>
</form>
`
})
export class ReactiveCheckboxComponent {
checkboxForm = new FormGroup({
selections: new FormControl([], [
Validators.required,
Validators.minLength(2),
Validators.maxLength(4)
])
});
options = ['Option 1', 'Option 2', 'Option 3', 'Option 4', 'Option 5'];
}Template-Driven Forms
import { Component } from '@angular/core';
@Component({
selector: 'app-template-checkbox',
template: `
<form #checkboxForm="ngForm">
<app-checkbox-selection-input
[(ngModel)]="selectedValues"
name="checkboxSelections"
[data]="options"
label="Template Driven Selection"
[minSelection]="1"
[maxSelection]="3"
required>
</app-checkbox-selection-input>
<div *ngIf="checkboxForm.controls.checkboxSelections?.invalid &&
checkboxForm.controls.checkboxSelections?.touched">
Please make a selection
</div>
</form>
`
})
export class TemplateCheckboxComponent {
selectedValues: string[] = [];
options = ['Choice 1', 'Choice 2', 'Choice 3', 'Choice 4'];
}Programmatic Control
// Setting values
this.formControl.setValue(['Option 1', 'Option 2']);
this.formControl.patchValue(['Option 1']); // Partial update
// Resetting
this.formControl.reset();
// Getting current value
const currentValue = this.formControl.value;
// Validation
const isValid = this.formControl.valid;
const errors = this.formControl.errors;
// Setting custom errors
this.formControl.setErrors({ customError: 'Custom error message' });Validation System
Built-in Validation
The component provides built-in validation for:
Min Selection Validation
// Requires at least X selections
[minSelection]="2" // Must select at least 2 itemsMax Selection Validation
// Allows maximum X selections
[maxSelection]="3" // Cannot select more than 3 itemsDisable Max Behavior
// Auto-disable remaining checkboxes when max reached
[disableMax]="true" // Disables unchecked boxes at maxValidation Error Types
| Error Type | Condition | Description |
|------------|-----------|-------------|
| minRequired | selectedCount < minSelection | Not enough selections made |
| maxExceeded | selectedCount >= maxSelection | Too many selections made |
| required | Control has required validator and no selections | Control is required but empty |
Custom Validation Examples
// Complex validation scenarios
const complexForm = new FormGroup({
permissions: new FormControl([], [
Validators.required,
Validators.minLength(1), // At least 1 selection
Validators.maxLength(5), // Maximum 5 selections
customMaxValidator(3) // Custom: Cannot select admin + delete together
])
});
// Custom validator example
function customMaxValidator(maxCount: number) {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value;
if (value && value.length > maxCount) {
return { customMaxExceeded: true };
}
return null;
};
}Module Configuration
CheckboxSelectionInputModule
No Global Configuration Required
The CheckboxSelectionInputModule does not provide a forRoot() method or global configuration options. All configuration is done at the component level through input properties.
Module Structure
@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
MatSliderModule,
MatButtonModule,
MatIconModule,
MatFormFieldModule,
MatToolbarModule,
MatCheckboxModule,
MatMenuModule,
MatButtonToggleModule,
MatDividerModule,
MatRadioModule,
MatInputModule,
MatAutocompleteModule,
RemoveUnderscorePipe,
MatSelectModule,
MatOptionModule,
MatSlideToggleModule,
],
declarations: [
CheckboxSelectionInputComponent,
CheckboxSelectionDemoComponent,
],
exports: [
CheckboxSelectionInputComponent,
CheckboxSelectionDemoComponent
]
})
export class CheckboxSelectionInputModule { }Dependencies
- @angular/common: Core Angular functionality
- @angular/forms: Form control integration (FormsModule, ReactiveFormsModule)
- @angular/material: Material Design components
- MatCheckboxModule: Checkbox components
- MatFormFieldModule: Form field styling
- MatButtonModule: Button components
- MatIconModule: Icon display
- MatSlideToggleModule: Toggle switches for demo
- MatDividerModule: Visual dividers
- Additional Material modules for comprehensive UI support
Styling and Customization
CSS Classes and Styling
The component uses Material Design styling and can be customized using:
- Global Material Theme: Configure colors in your Angular Material theme
- Component-specific Styles: Add custom CSS classes
- Form Field Styling: Style using Material form field classes
- Checkbox Styling: Customize individual checkbox appearance
Custom Styling Examples
// Custom checkbox group styling
:host ::ng-deep .checkbox-selection-input {
.mat-form-field {
.mat-form-field-label {
color: #2196f3;
font-weight: 500;
}
}
.mat-checkbox {
margin-bottom: 8px;
&.mat-checkbox-disabled {
.mat-checkbox-label {
opacity: 0.6;
}
}
}
}
// Custom disabled state styling
:host ::ng-deep .checkbox-selection-input {
.mat-checkbox-disabled {
.mat-checkbox-frame {
border-color: #ccc;
}
.mat-checkbox-label {
color: #999;
}
}
}Layout Customization
// Horizontal layout
.horizontal-checkboxes {
app-checkbox-selection-input {
.mat-checkbox {
display: inline-block;
margin-right: 16px;
margin-bottom: 0;
}
}
}
// Compact layout
.compact-checkboxes {
app-checkbox-selection-input {
.mat-checkbox {
margin-bottom: 4px;
.mat-checkbox-label {
font-size: 0.875rem;
}
}
}
}Accessibility
ARIA Support
- Checkboxes include proper ARIA labels and roles
- Group labeling through label input property
- Keyboard navigation is fully supported (Tab, Space, Arrow keys)
- Screen reader friendly with appropriate descriptions
- Validation errors are announced to assistive technologies
- Disabled states are properly communicated
Best Practices
- Provide meaningful labels for the checkbox group
- Use descriptive placeholders for additional context
- Set appropriate validation messages for accessibility
- Consider keyboard navigation order
- Test with screen readers to ensure proper announcements
- Use logical grouping for related checkbox options
Keyboard Navigation
| Key | Action |
|-----|--------|
| Tab | Navigate to next checkbox |
| Shift+Tab | Navigate to previous checkbox |
| Space | Toggle checkbox selection |
| Enter | Toggle checkbox selection (in some contexts) |
Integration Examples
With Other UI Components
// Integration with display-card
@Component({
template: `
<app-display-card title="User Permissions">
<app-checkbox-selection-input
[data]="permissionOptions"
[formControl]="permissionControl"
label="Select Permissions"
[minSelection]="1"
[maxSelection]="3"
[disableMax]="true">
</app-checkbox-selection-input>
</app-display-card>
`
})
export class CardWithCheckboxComponent {
permissionControl = new FormControl();
permissionOptions = [
{ id: 1, value: 'Read Access' },
{ id: 2, value: 'Write Access' },
{ id: 3, value: 'Delete Access' },
{ id: 4, value: 'Admin Access' }
];
}With State Management
// Integration with HTTP Request Manager
@Component({
template: `
<app-checkbox-selection-input
[data]="categoryOptions$ | async"
[formControl]="categoryControl"
[multiple]="true"
(selectionChange)="handleSelectionChange($event)">
</app-checkbox-selection-input>
`
})
export class StateManagedCheckboxComponent {
categoryOptions$ = this.categoryStore.options$;
categoryControl = new FormControl();
constructor(private categoryStore: CategoryStore) {}
handleSelectionChange(selectedValues: string[]) {
this.categoryStore.updateSelection(selectedValues);
}
}With Dynamic Forms
@Component({
template: `
<div formArrayName="checkboxGroups">
<div *ngFor="let group of checkboxGroups.controls; let i = index">
<app-checkbox-selection-input
[formControlName]="i"
[data]="dynamicOptions[i]"
[label]="'Group ' + (i + 1)">
</app-checkbox-selection-input>
</div>
</div>
`
})
export class DynamicFormCheckboxComponent {
checkboxForm = this.fb.group({
checkboxGroups: this.fb.array([
this.fb.control(['option1']),
this.fb.control(['option2', 'option3']),
this.fb.control([])
])
});
get checkboxGroups() {
return this.checkboxForm.get('checkboxGroups') as FormArray;
}
dynamicOptions = [
['Option 1A', 'Option 1B', 'Option 1C'],
['Option 2A', 'Option 2B'],
['Option 3A', 'Option 3B', 'Option 3C', 'Option 3D']
];
}Performance Optimization
Performance Tips
- Use OnPush change detection for better performance with large checkbox arrays
- Implement trackBy for dynamic checkbox lists (if applicable)
- Avoid frequent data object recreation to prevent unnecessary re-renders
- Use immutable data patterns for checkbox option updates
- Consider virtual scrolling for very large checkbox lists
- Optimize validation to avoid expensive operations on every change
Memory Management
// Efficient data updates
updateOptions(newOptions: any[]) {
// Create new array reference to trigger change detection
this.options = [...newOptions];
}
// Cleanup in ngOnDestroy
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}Testing
Unit Testing Example
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CheckboxSelectionInputComponent } from './checkbox-selection-input.component';
import { ReactiveFormsModule } from '@angular/forms';
describe('CheckboxSelectionInputComponent', () => {
let component: CheckboxSelectionInputComponent;
let fixture: ComponentFixture<CheckboxSelectionInputComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ CheckboxSelectionInputComponent ],
imports: [ ReactiveFormsModule ]
}).compileComponents();
fixture = TestBed.createComponent(CheckboxSelectionInputComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should display checkboxes from data input', () => {
component.data = ['Option 1', 'Option 2', 'Option 3'];
fixture.detectChanges();
const compiled = fixture.nativeElement;
const checkboxes = compiled.querySelectorAll('.mat-checkbox');
expect(checkboxes.length).toBe(3);
});
it('should emit selection changes', () => {
spyOn(component.selectionChange, 'emit');
component.data = ['Option 1', 'Option 2'];
component.writeValue(['Option 1']);
fixture.detectChanges();
expect(component.selectionChange.emit).toHaveBeenCalledWith(['Option 1']);
});
it('should validate minimum selections', () => {
component.minSelection = 2;
component.data = ['Option 1', 'Option 2', 'Option 3'];
// Simulate selecting only 1 item
component.writeValue(['Option 1']);
const validationResult = component.validate({} as AbstractControl);
expect(validationResult?.minRequired).toBe(true);
});
it('should validate maximum selections', () => {
component.maxSelection = 2;
component.data = ['Option 1', 'Option 2', 'Option 3'];
// Simulate selecting 3 items when max is 2
component.writeValue(['Option 1', 'Option 2', 'Option 3']);
const validationResult = component.validate({} as AbstractControl);
expect(validationResult?.maxExceeded).toBe(true);
});
});Integration Testing
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { CheckboxSelectionInputModule } from './checkbox-selection-input.module';
describe('CheckboxSelectionInput Integration', () => {
let component: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
@Component({
template: `
<app-checkbox-selection-input
[formControl]="testControl"
[data]="testData"
[minSelection]="1"
[maxSelection]="2">
</app-checkbox-selection-input>
`
})
class TestHostComponent {
testControl = new FormControl();
testData = ['Test 1', 'Test 2', 'Test 3'];
}
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TestHostComponent ],
imports: [ CheckboxSelectionInputModule ]
}).compileComponents();
fixture = TestBed.createComponent(TestHostComponent);
component = fixture.componentInstance;
});
it('should integrate with form controls', () => {
expect(component.testControl).toBeDefined();
expect(component.testData.length).toBe(3);
});
it('should update form control value when selection changes', () => {
fixture.detectChanges();
// Simulate user interaction
const checkboxes = fixture.nativeElement.querySelectorAll('.mat-checkbox');
checkboxes[0].click(); // Select first checkbox
expect(component.testControl.value).toEqual(['Test 1']);
});
it('should enforce validation constraints', () => {
fixture.detectChanges();
// Test minimum selection validation
component.testControl.setValue([]);
expect(component.testControl.valid).toBe(false);
// Test maximum selection validation
component.testControl.setValue(['Test 1', 'Test 2', 'Test 3']);
expect(component.testControl.valid).toBe(false);
// Test valid selection
component.testControl.setValue(['Test 1', 'Test 2']);
expect(component.testControl.valid).toBe(true);
});
});Troubleshooting
Common Issues
- Form control not working: Ensure ReactiveFormsModule is imported
- Validation not triggering: Check that validators are properly configured
- Selection not updating: Verify data format matches expected structure
- Styling issues: Ensure Material theme is properly configured
- Auto-disable not working: Check disableMax input property
- Performance issues: Consider OnPush change detection for large datasets
Debug Mode
// Add debugging to track form control changes and validation
@Component({
template: `
<div class="debug-info">
Form Control Value: {{ formControl.value | json }}<br>
Form Control Valid: {{ formControl.valid }}<br>
Form Control Errors: {{ formControl.errors | json }}<br>
Data Length: {{ data?.length || 0 }}<br>
Min Selection: {{ minSelection }}<br>
Max Selection: {{ maxSelection }}<br>
Disable Max: {{ disableMax }}
</div>
<app-checkbox-selection-input
[formControl]="formControl"
[data]="data"
[minSelection]="minSelection"
[maxSelection]="maxSelection"
[disableMax]="disableMax">
</app-checkbox-selection-input>
`
})
export class DebugCheckboxComponent {
formControl = new FormControl();
data: string[] = [];
minSelection = 0;
maxSelection = 0;
disableMax = false;
constructor() {
this.formControl.valueChanges.subscribe(value => {
console.log('Checkbox value changed:', value);
});
this.formControl.statusChanges.subscribe(status => {
console.log('Form control status:', status);
});
}
}Validation Debugging
// Debug validation logic
validate(control: AbstractControl): ValidationErrors | null {
console.log('Validating with:', {
selectedCount: this.selectedCheckboxes(this.selectionControl.value).length,
minSelection: this.minSelection,
maxSelection: this.maxSelection,
currentValue: this.selectionControl.value
});
// ... validation logic
const errors = this.selectedValues(this.selectionControl.value, true).length > 0 ? errors : null;
console.log('Validation result:', errors);
return errors;
}Performance Debugging
// Monitor change detection performance
ngAfterViewInit() {
// Track rendering time
const start = performance.now();
setTimeout(() => {
const end = performance.now();
console.log(`Checkbox rendering took ${end - start}ms`);
});
}Advanced Usage Patterns
Conditional Validation
// Complex validation scenarios
@Component({
template: `
<app-checkbox-selection-input
[formControl]="conditionalControl"
[data]="conditionalOptions"
[minSelection]="getMinSelection()"
[maxSelection]="getMaxSelection()"
[disableMax]="shouldDisableMax()">
</app-checkbox-selection-input>
`
})
export class ConditionalCheckboxComponent {
conditionalControl = new FormControl();
getMinSelection(): number {
const userRole = this.getUserRole();
return userRole === 'admin' ? 1 : 2;
}
getMaxSelection(): number {
const subscriptionLevel = this.getSubscriptionLevel();
return subscriptionLevel === 'premium' ? 5 : 3;
}
shouldDisableMax(): boolean {
return true;
}
}Data Transformation
// Transform data before passing to component
transformData(rawData: any[]): any[] {
return rawData.map(item => {
if (typeof item === 'string') {
return {
id: this.generateId(item),
value: item,
selected: this.isPreSelected(item),
disabled: this.isDisabled(item)
};
}
return SelectionItem.adapt(item);
});
}Custom Validation Messages
// Dynamic error messages
getErrorMessage(control: AbstractControl): string {
if (control.hasError('minRequired')) {
const min = this.minSelection;
return `Please select at least ${min} option${min > 1 ? 's' : ''}`;
}
if (control.hasError('maxExceeded')) {
const max = this.maxSelection;
return `You can select maximum ${max} option${max > 1 ? 's' : ''}`;
}
if (control.hasError('required')) {
return 'Please make a selection';
}
return 'Invalid selection';
}