New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

checkbox-selection-input

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

checkbox-selection-input

This is an Angular Module containing Components/Services using Material

latest
npmnpm
Version
15.0.5
Version published
Weekly downloads
0
-100%
Maintainers
1
Weekly downloads
 
Created
Source

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

FeatureDescription
Flexible SelectionSupport for multiple item selection with validation
Rich ValidationBuilt-in min/max selection limits with error states
Data Format SupportWorks with simple strings or complex objects
State ManagementDisabled states and pre-selection capabilities
Form IntegrationSeamless Angular form control and validation integration
Material DesignConsistent 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

InputTypeDescriptionDefault
dataany[] | string[]Array of items or strings defining checkbox options(Required)
labelstringOptional label text for the checkbox groupundefined
placeholderstringOptional placeholder textundefined
errorstringOptional error message to displayundefined
disableMaxbooleanIf true, disables checkboxes when max selection is reachedfalse
useDefaultResetbooleanUse default reset behaviorfalse
minSelectionnumberMinimum number of selections required0
maxSelectionnumberMaximum number of selections allowed0

Outputs

OutputTypeDescription
selectionChangeEventEmitter<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 TypeConditionDescription
minRequiredselectedCount < minSelectionNot enough selections made
maxExceededselectedCount >= maxSelectionToo many selections made
requiredControl has required validator and no selectionsControl 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

KeyAction
TabNavigate to next checkbox
Shift+TabNavigate to previous checkbox
SpaceToggle checkbox selection
EnterToggle 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';
}

FAQs

Package last updated on 28 Dec 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts