npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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 items

Max Selection Validation

// Allows maximum X selections
[maxSelection]="3"  // Cannot select more than 3 items

Disable Max Behavior

// Auto-disable remaining checkboxes when max reached
[disableMax]="true"  // Disables unchecked boxes at max

Validation 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:

  1. Global Material Theme: Configure colors in your Angular Material theme
  2. Component-specific Styles: Add custom CSS classes
  3. Form Field Styling: Style using Material form field classes
  4. 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

  1. Provide meaningful labels for the checkbox group
  2. Use descriptive placeholders for additional context
  3. Set appropriate validation messages for accessibility
  4. Consider keyboard navigation order
  5. Test with screen readers to ensure proper announcements
  6. 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

  1. Use OnPush change detection for better performance with large checkbox arrays
  2. Implement trackBy for dynamic checkbox lists (if applicable)
  3. Avoid frequent data object recreation to prevent unnecessary re-renders
  4. Use immutable data patterns for checkbox option updates
  5. Consider virtual scrolling for very large checkbox lists
  6. 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

  1. Form control not working: Ensure ReactiveFormsModule is imported
  2. Validation not triggering: Check that validators are properly configured
  3. Selection not updating: Verify data format matches expected structure
  4. Styling issues: Ensure Material theme is properly configured
  5. Auto-disable not working: Check disableMax input property
  6. 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';
}