radio-selection-input
v15.0.5
Published
This is an Angular Module containing Components/Services using Material
Readme
Radio Selection Input Component
Overview
The radio-selection-input library provides a Material Design radio button selection component that allows users to select exactly one option from a mutually exclusive set of choices. It supports both string and object data formats, implements ControlValueAccessor for seamless form integration, and provides native Angular validation system compatibility.
Core Capabilities
🔘 Radio Button Selection Interface
- Exclusive Selection: Only one option can be selected at a time (radio button behavior)
- Form Control Integration: Implements
ControlValueAccessorfor reactive forms - Flexible Data Support: Handles both string arrays and complex object arrays
- Material Design: Built on Angular Material radio button foundation
- Validation Support: Native Angular validation system compatibility
- Label Configuration: Customizable labels and placeholders
- Disabled State Support: Mark individual options as non-selectable
- Pre-selection: Initialize with a selected option
🔧 Features
✅ ControlValueAccessor Implementation - Works with Angular forms
✅ Material Design Integration - Uses Angular Material components
✅ Exclusive Selection - Single selection from mutually exclusive options
✅ Flexible Data Types - Support for strings and objects
✅ Form Validation - Native validation integration
✅ Customizable Labels - Configurable display labels
✅ Disabled Options - Mark individual options as non-selectable
✅ Pre-selection - Initialize with selected option
✅ Required Validation - Ensure selection is made
Key Benefits
| Feature | Description | |---------|-------------| | Exclusive Selection | Ensures only one option can be selected | | Form Integration | Seamless Angular form control integration | | Data Format Support | Works with simple strings or complex objects | | Validation Ready | Built-in validation support | | Material Design | Consistent Material Design styling | | Accessibility | Full ARIA support and keyboard navigation |
Summary
The radio-selection-input library provides a traditional radio button selection component with comprehensive form integration and validation support, perfect for mutually exclusive selection scenarios in Angular applications.
Quick Start Guide
Installation & Setup (2 minutes)
1. Import Module
// app.module.ts
import { RadioSelectionInputModule } from 'radio-selection-input';
@NgModule({
imports: [
RadioSelectionInputModule
]
})
export class AppModule { }2. No Module Configuration Required
The RadioSelectionInputModule does not require global configuration. Components can be used immediately after module import.
Quick Examples
Example 1: Basic String Array Selection
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-basic-radio',
template: `
<app-radio-selection-input
[formControl]="selectionControl"
[data]="paymentMethods"
label="Payment Method"
placeholder="Choose payment method">
</app-radio-selection-input>
<div>Selected: {{ selectionControl.value }}</div>
`
})
export class BasicRadioComponent {
selectionControl = new FormControl();
paymentMethods = [
'Credit Card', 'Debit Card', 'PayPal', 'Bank Transfer', 'Cash on Delivery'
];
}Example 2: Object Array Selection
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-object-radio',
template: `
<app-radio-selection-input
[formControl]="userControl"
[data]="userOptions"
label="Select User"
placeholder="Choose a user"
displayField="label">
</app-radio-selection-input>
<div *ngIf="userControl.value" class="user-info">
<h4>{{ userControl.value.label }}</h4>
<p>{{ userControl.value.email }}</p>
<p>Role: {{ userControl.value.role }}</p>
</div>
`,
styles: [`
.user-info {
margin-top: 1rem;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
background: #f9f9f9;
}
.user-info h4 {
margin: 0 0 0.5rem 0;
color: #333;
}
.user-info p {
margin: 0.25rem 0;
font-size: 0.9rem;
color: #666;
}
`]
})
export class ObjectRadioComponent {
userControl = new FormControl();
userOptions = [
{ value: 'user1', label: 'John Doe', email: '[email protected]', role: 'Admin' },
{ value: 'user2', label: 'Jane Smith', email: '[email protected]', role: 'Editor' },
{ value: 'user3', label: 'Bob Johnson', email: '[email protected]', role: 'Viewer' },
{ value: 'user4', label: 'Alice Brown', email: '[email protected]', role: 'Editor' }
];
}Example 3: Required Selection with Validation
import { Component } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-required-radio',
template: `
<app-radio-selection-input
[formControl]="sizeControl"
[data]="shirtSizes"
label="Shirt Size"
placeholder="Select your shirt size"
[required]="true">
</app-radio-selection-input>
<div class="errors" *ngIf="sizeControl.errors && sizeControl.touched">
<div *ngIf="sizeControl.hasError('required')">
Shirt size selection is required
</div>
</div>
<div>Selected Size: {{ sizeControl.value }}</div>
`,
styles: [`
.errors {
color: #f44336;
font-size: 0.875rem;
margin-top: 0.5rem;
}
`]
})
export class RequiredRadioComponent {
sizeControl = new FormControl('', Validators.required);
shirtSizes = ['XS', 'S', 'M', 'L', 'XL', 'XXL'];
}Example 4: Radio Group with Pre-selection
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-pre-selected-radio',
template: `
<app-radio-selection-input
[formControl]="themeControl"
[data]="themeOptions"
label="Theme Preference"
placeholder="Choose a theme">
</app-radio-selection-input>
<div class="theme-preview" [attr.data-theme]="themeControl.value">
<h4>Theme Preview</h4>
<p>This is how your selected theme would look.</p>
<button class="sample-button">Sample Button</button>
</div>
`,
styles: [`
.theme-preview {
margin-top: 1rem;
padding: 1rem;
border-radius: 8px;
transition: all 0.3s ease;
}
.theme-preview[data-theme="light"] {
background: #ffffff;
color: #333333;
border: 1px solid #e0e0e0;
}
.theme-preview[data-theme="dark"] {
background: #333333;
color: #ffffff;
border: 1px solid #555555;
}
.theme-preview[data-theme="auto"] {
background: #f5f5f5;
color: #666666;
border: 1px solid #d0d0d0;
}
.sample-button {
margin-top: 0.5rem;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.theme-preview[data-theme="light"] .sample-button {
background: #2196f3;
color: white;
}
.theme-preview[data-theme="dark"] .sample-button {
background: #64b5f6;
color: #333333;
}
.theme-preview[data-theme="auto"] .sample-button {
background: #ff9800;
color: white;
}
`]
})
export class PreSelectedRadioComponent {
themeControl = new FormControl('light');
themeOptions = [
{ value: 'light', label: 'Light Theme', description: 'Bright and clean' },
{ value: 'dark', label: 'Dark Theme', description: 'Easy on the eyes' },
{ value: 'auto', label: 'Auto', description: 'Follow system preference' }
];
}Example 5: Form Integration with Dynamic Options
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-dynamic-radio-form',
template: `
<form [formGroup]="dynamicForm">
<app-radio-selection-input
formControlName="plan"
[data]="subscriptionPlans"
label="Subscription Plan"
placeholder="Select a plan"
[required]="true">
</app-radio-selection-input>
<app-radio-selection-input
formControlName="billingCycle"
[data]="billingCycles"
label="Billing Cycle"
placeholder="Choose billing frequency"
[required]="true">
</app-radio-selection-input>
</form>
<div class="form-status">
<div>Form Valid: {{ dynamicForm.valid }}</div>
<div>Plan: {{ dynamicForm.get('plan')?.value }}</div>
<div>Billing: {{ dynamicForm.get('billingCycle')?.value }}</div>
</div>
<div class="summary" *ngIf="dynamicForm.valid">
<h4>Subscription Summary</h4>
<p><strong>Plan:</strong> {{ getPlanDetails(dynamicForm.get('plan')?.value)?.name }}</p>
<p><strong>Billing:</strong> {{ getPlanDetails(dynamicForm.get('plan')?.value)?.billing }}</p>
<p><strong>Price:</strong> ${{ getPlanPrice(dynamicForm.get('plan')?.value, dynamicForm.get('billingCycle')?.value) }}/month</p>
</div>
<div class="controls">
<button (click)="selectPopularPlan()">Select Popular Plan</button>
<button (click)="resetForm()">Reset</button>
<button (click)="submitForm()" [disabled]="dynamicForm.invalid">Subscribe</button>
</div>
`,
styles: [`
form {
display: flex;
flex-direction: column;
gap: 1.5rem;
max-width: 400px;
}
.form-status {
margin-top: 1rem;
padding: 1rem;
background: #f5f5f5;
border-radius: 4px;
font-family: monospace;
font-size: 0.9rem;
}
.summary {
margin-top: 1rem;
padding: 1rem;
border: 2px solid #4caf50;
border-radius: 8px;
background: #f1f8e9;
}
.summary h4 {
margin: 0 0 0.5rem 0;
color: #2e7d32;
}
.summary p {
margin: 0.25rem 0;
color: #388e3c;
}
.controls {
margin-top: 1rem;
display: flex;
gap: 0.5rem;
}
button {
padding: 0.5rem 1rem;
border: 1px solid #ccc;
border-radius: 4px;
background: white;
cursor: pointer;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`]
})
export class DynamicRadioFormComponent {
dynamicForm: FormGroup;
subscriptionPlans = [
{ value: 'basic', name: 'Basic Plan', price: 9.99, billing: 'Monthly' },
{ value: 'pro', name: 'Pro Plan', price: 19.99, billing: 'Monthly' },
{ value: 'enterprise', name: 'Enterprise Plan', price: 49.99, billing: 'Monthly' }
];
billingCycles = ['Monthly', 'Quarterly', 'Yearly'];
constructor(private fb: FormBuilder) {
this.dynamicForm = this.fb.group({
plan: ['', Validators.required],
billingCycle: ['', Validators.required]
});
}
getPlanDetails(planValue: string) {
return this.subscriptionPlans.find(plan => plan.value === planValue);
}
getPlanPrice(planValue: string, billingCycle: string) {
const plan = this.getPlanDetails(planValue);
if (!plan) return 0;
const multipliers = {
'Monthly': 1,
'Quarterly': 0.9, // 10% discount
'Yearly': 0.8 // 20% discount
};
return (plan.price * (multipliers[billingCycle as keyof typeof multipliers] || 1)).toFixed(2);
}
selectPopularPlan() {
this.dynamicForm.patchValue({
plan: 'pro',
billingCycle: 'Yearly'
});
}
resetForm() {
this.dynamicForm.reset();
}
submitForm() {
if (this.dynamicForm.valid) {
console.log('Subscription form submitted:', this.dynamicForm.value);
alert('Subscription created successfully!');
}
}
}Component API
Inputs
| Input | Type | Description | Default |
| :--- | :--- | :--- | :--- |
| data | any[] \| string[] | Array of options to select from | (Required) |
| label | string | Label text for the radio group | undefined |
| placeholder | string | Placeholder text for the radio group | undefined |
| displayField | string | Property name to display for object arrays | undefined |
| required | boolean | Whether selection is required | false |
| disabled | boolean | Whether the radio group is disabled | false |
| direction | 'row' \| 'column' | Layout direction for radio options | 'column' |
Outputs
| Output | Type | Description |
|--------|------|-------------|
| selectionChange | EventEmitter<any> | Emits selected value when selection changes |
Form Integration (ControlValueAccessor)
The component implements Angular's ControlValueAccessor interface for seamless form integration.
ControlValueAccessor Implementation
// writeValue(value: any): void
// Sets the selected value
writeValue(value: any): void {
this.selectedValue = value;
}
// 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
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}Form Integration Examples
Reactive Forms
import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-reactive-radio-form',
template: `
<form [formGroup]="radioForm">
<app-radio-selection-input
formControlName="gender"
[data]="genderOptions"
label="Gender"
placeholder="Select gender"
[required]="true">
</app-radio-selection-input>
<app-radio-selection-input
formControlName="preferredContact"
[data]="contactOptions"
label="Preferred Contact Method"
placeholder="Choose contact method">
</app-radio-selection-input>
</form>
`
})
export class ReactiveRadioFormComponent {
radioForm = new FormGroup({
gender: new FormControl('', Validators.required),
preferredContact: new FormControl('')
});
genderOptions = ['Male', 'Female', 'Other', 'Prefer not to say'];
contactOptions = ['Email', 'Phone', 'SMS', 'Mail'];
}Template-Driven Forms
import { Component } from '@angular/core';
@Component({
selector: 'app-template-radio-form',
template: `
<app-radio-selection-input
[(ngModel)]="selectedOption"
name="templateRadio"
[data]="templateOptions"
label="Template Radio Selection"
placeholder="Choose an option"
required>
</app-radio-selection-input>
<div *ngIf="selectedOption">
Selected: {{ selectedOption }}
</div>
`
})
export class TemplateRadioFormComponent {
selectedOption = '';
templateOptions = ['Option A', 'Option B', 'Option C', 'Option D'];
}Model Structures
Radio Option Interface
export interface RadioOptionInterface {
value: any; // The value to be selected/returned
label?: string; // Optional display label
disabled?: boolean; // Whether this option is disabled
selected?: boolean; // Whether this option is pre-selected
description?: string; // Optional description text
}Radio Option Class
export class RadioOption implements RadioOptionInterface {
constructor(
public value: any,
public label?: string,
public disabled?: boolean = false,
public selected?: boolean = false,
public description?: string,
) {}
static adapt(item?: any): RadioOption {
return new RadioOption(
item?.value ?? item,
item?.label,
item?.disabled || false,
item?.selected || false,
item?.description,
);
}
}Usage Examples
// String array data (automatically converted)
const stringData = ['Option 1', 'Option 2', 'Option 3'];
// Object array data
const objectData = [
{ value: 'opt1', label: 'Option 1', disabled: false, selected: false, description: 'First option' },
{ value: 'opt2', label: 'Option 2', disabled: true, selected: false, description: 'Second option (disabled)' },
{ value: 'opt3', label: 'Option 3', disabled: false, selected: true, description: 'Third option (pre-selected)' }
];
// Manual RadioOption creation
const manualOptions = [
new RadioOption('value1', 'Label 1', false, false, 'Description 1'),
new RadioOption('value2', 'Label 2', true, false, 'Description 2'),
new RadioOption('value3', 'Label 3', false, true, 'Description 3')
];
// Using adapt method for flexible data
const adaptedOptions = [
RadioOption.adapt({ value: 'item1', label: 'Item 1', selected: true }),
RadioOption.adapt({ value: 'item2', label: 'Item 2', disabled: true }),
RadioOption.adapt('item3') // String fallback
];Validation System
Built-in Validation
The component supports Angular's validation system:
Required Validation
// Marks field as required
[required]="true" // Selection must be madeCustom Validators
// Add custom validation logic
const customForm = new FormGroup({
selection: new FormControl('', [
Validators.required,
customValidator
])
});
// Custom validator example
function customValidator(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if (value === 'blocked-option') {
return { blockedOption: true };
}
return null;
}Validation Error Types
| Error Type | Condition | Description |
|------------|-----------|-------------|
| required | Control has required validator and no selection | Control is required but empty |
| custom | Custom validation logic fails | User-defined validation errors |
Module Configuration
RadioSelectionInputModule
No Global Configuration Required
The RadioSelectionInputModule does not provide a forRoot() method or global configuration options. All configuration is done at the component level through input properties.
Dependencies
- @angular/core: Core Angular functionality
- @angular/forms: Form control integration (FormsModule, ReactiveFormsModule)
- @angular/material: Material Design components (MatRadioModule, MatFormFieldModule, etc.)
Advanced Usage Patterns
Conditional Options
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-conditional-radio',
template: `
<app-radio-selection-input
[formControl]="accountTypeControl"
[data]="accountTypes"
label="Account Type"
placeholder="Select account type">
</app-radio-selection-input>
<app-radio-selection-input
[formControl]="planControl"
[data]="availablePlans"
label="Subscription Plan"
placeholder="Choose a plan"
[disabled]="!accountTypeControl.value">
</app-radio-selection-input>
<div class="plan-details" *ngIf="planControl.value">
<h4>Plan Details</h4>
<p>{{ getPlanDetails(planControl.value)?.description }}</p>
<p><strong>Price:</strong> ${{ getPlanDetails(planControl.value)?.price }}/month</p>
</div>
`
})
export class ConditionalRadioComponent {
accountTypeControl = new FormControl();
planControl = new FormControl();
accountTypes = [
{ value: 'individual', label: 'Individual', description: 'Personal use' },
{ value: 'business', label: 'Business', description: 'Commercial use' },
{ value: 'enterprise', label: 'Enterprise', description: 'Large organizations' }
];
availablePlans: any[] = [];
constructor() {
this.accountTypeControl.valueChanges.subscribe(accountType => {
this.updateAvailablePlans(accountType);
this.planControl.reset();
});
}
updateAvailablePlans(accountType: string) {
const planMap: { [key: string]: any[] } = {
'individual': [
{ value: 'basic', label: 'Basic', description: 'Essential features', price: 9.99 },
{ value: 'premium', label: 'Premium', description: 'Advanced features', price: 19.99 }
],
'business': [
{ value: 'starter', label: 'Starter', description: 'Business basics', price: 29.99 },
{ value: 'professional', label: 'Professional', description: 'Full business suite', price: 59.99 },
{ value: 'enterprise', label: 'Enterprise', description: 'Complete solution', price: 99.99 }
],
'enterprise': [
{ value: 'custom', label: 'Custom', description: 'Tailored solution', price: 0 }
]
};
this.availablePlans = planMap[accountType] || [];
}
getPlanDetails(planValue: string) {
return this.availablePlans.find(plan => plan.value === planValue);
}
}Accessibility Enhancement
import { Component } from '@angular/core';
@Component({
selector: 'app-accessible-radio',
template: `
<app-radio-selection-input
[formControl]="accessibilityControl"
[data]="accessibilityOptions"
label="Accessibility Preference"
placeholder="Choose accessibility option"
[ariaLabel]="'Accessibility settings for screen readers'"
[ariaDescribedBy]="'accessibility-description'">
</app-radio-selection-input>
<div id="accessibility-description" class="sr-only">
These options control accessibility features like screen reader support and keyboard navigation.
</div>
<div class="accessibility-info" *ngIf="accessibilityControl.value">
<h4>Accessibility Information</h4>
<p>{{ getAccessibilityInfo(accessibilityControl.value) }}</p>
</div>
`,
styles: [`
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.accessibility-info {
margin-top: 1rem;
padding: 1rem;
background: #e3f2fd;
border: 1px solid #2196f3;
border-radius: 4px;
}
`]
})
export class AccessibleRadioComponent {
accessibilityControl = new FormControl();
accessibilityOptions = [
{
value: 'high-contrast',
label: 'High Contrast Mode',
description: 'Enhanced colors for better visibility',
info: 'Increases color contrast and uses thicker borders for better visibility.'
},
{
value: 'large-text',
label: 'Large Text',
description: 'Bigger fonts throughout the interface',
info: 'Increases font sizes for easier reading.'
},
{
value: 'reduced-motion',
label: 'Reduced Motion',
description: 'Minimizes animations and transitions',
info: 'Disables non-essential animations to reduce motion sensitivity.'
},
{
value: 'screen-reader',
label: 'Screen Reader Optimized',
description: 'Enhanced screen reader support',
info: 'Provides additional labels and descriptions for screen readers.'
}
];
getAccessibilityInfo(value: string) {
const option = this.accessibilityOptions.find(opt => opt.value === value);
return option?.info || '';
}
}Performance Optimization
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-optimized-radio',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-radio-selection-input
[formControl]="optimizedControl"
[data]="largeDataset"
label="Large Dataset Selection"
placeholder="Select from large dataset..."
[filterFunction]="customFilter">
</app-radio-selection-input>
`
})
export class OptimizedRadioComponent {
optimizedControl = new FormControl();
// Large dataset with efficient filtering
largeDataset = Array.from({ length: 1000 }, (_, i) => ({
id: i,
name: `Option ${i}`,
category: `Category ${Math.floor(i / 100)}`,
disabled: i % 10 === 0, // Every 10th option is disabled
selected: i === 0 // First option is pre-selected
}));
customFilter(data: any[], filterText: string): any[] {
if (!filterText) return data;
const lowerFilter = filterText.toLowerCase();
return data.filter(item =>
item.name.toLowerCase().includes(lowerFilter) ||
item.category.toLowerCase().includes(lowerFilter)
);
}
}Integration Examples
With Other UI Components
import { Component } from '@angular/core';
@Component({
selector: 'app-integrated-radio',
template: `
<app-display-card title="Shipping Method">
<app-radio-selection-input
[formControl]="shippingControl"
[data]="shippingMethods"
label="Shipping Method"
placeholder="Choose shipping option"
displayField="label">
</app-radio-selection-input>
<div *ngIf="shippingControl.value" class="shipping-details">
<h4>{{ shippingControl.value.label }}</h4>
<p>{{ shippingControl.value.description }}</p>
<p><strong>Cost:</strong> {{ shippingControl.value.cost | currency }}</p>
<p><strong>Estimated Delivery:</strong> {{ shippingControl.value.estimatedDays }} business days</p>
</div>
</app-display-card>
`
})
export class IntegratedRadioComponent {
shippingControl = new FormControl();
shippingMethods = [
{
value: 'standard',
label: 'Standard Shipping',
description: 'Regular delivery service',
cost: 5.99,
estimatedDays: '5-7'
},
{
value: 'express',
label: 'Express Shipping',
description: 'Fast delivery service',
cost: 12.99,
estimatedDays: '2-3'
},
{
value: 'overnight',
label: 'Overnight Shipping',
description: 'Next day delivery',
cost: 24.99,
estimatedDays: '1'
},
{
value: 'pickup',
label: 'Store Pickup',
description: 'Pick up at local store',
cost: 0,
estimatedDays: '1-2'
}
];
}With State Management
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
@Component({
selector: 'app-state-radio',
template: `
<app-radio-selection-input
[formControl]="currencyControl"
[data]="currencies$ | async"
label="Preferred Currency"
placeholder="Select currency"
(selectionChange)="onCurrencySelected($event)">
</app-radio-selection-input>
`
})
export class StateRadioComponent {
currencyControl = new FormControl();
currencies$ = this.store.select(state => state.currencies);
constructor(private store: Store) {}
onCurrencySelected(currency: any) {
this.store.dispatch(selectCurrency({ currency }));
}
}Testing
Unit Testing Example
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RadioSelectionInputComponent } from './radio-selection-input.component';
import { ReactiveFormsModule } from '@angular/forms';
describe('RadioSelectionInputComponent', () => {
let component: RadioSelectionInputComponent;
let fixture: ComponentFixture<RadioSelectionInputComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ RadioSelectionInputComponent ],
imports: [ ReactiveFormsModule ]
}).compileComponents();
fixture = TestBed.createComponent(RadioSelectionInputComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should display radio options from data input', () => {
component.data = ['Option 1', 'Option 2', 'Option 3'];
fixture.detectChanges();
const compiled = fixture.nativeElement;
const radioInputs = compiled.querySelectorAll('input[type="radio"]');
expect(radioInputs.length).toBe(3);
});
it('should handle single selection (radio behavior)', () => {
component.data = ['Option 1', 'Option 2', 'Option 3'];
fixture.detectChanges();
const compiled = fixture.nativeElement;
const radioInputs = compiled.querySelectorAll('input[type="radio"]');
// Select first option
radioInputs[0].click();
fixture.detectChanges();
expect(component.selectedValue).toBe('Option 1');
// Select second option (should deselect first)
radioInputs[1].click();
fixture.detectChanges();
expect(component.selectedValue).toBe('Option 2');
expect(radioInputs[0].checked).toBe(false);
expect(radioInputs[1].checked).toBe(true);
});
it('should emit selection changes', () => {
spyOn(component.selectionChange, 'emit');
component.onSelectionChange('Option 1');
expect(component.selectionChange.emit).toHaveBeenCalledWith('Option 1');
});
it('should handle form control integration', () => {
const formControl = new FormControl();
component.formControl = formControl;
spyOn(formControl, 'setValue');
component.writeValue('Test Value');
expect(formControl.setValue).toHaveBeenCalledWith('Test Value');
});
});Troubleshooting
Common Issues
- Form control not working: Ensure ReactiveFormsModule is imported
- Selection not updating: Check that data format matches expected structure
- Object display issues: Set
displayFieldproperty for object arrays - Multiple selections: Radio buttons only support single selection by design
- Styling issues: Verify Material theme is properly configured
- Validation not working: Ensure validators are properly configured
Debug Mode
@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>
Selected Value: {{ selectedValue | json }}<br>
Required: {{ required }}
</div>
<app-radio-selection-input
[formControl]="formControl"
[data]="data"
[required]="required"
(selectionChange)="onSelectionChange($event)">
</app-radio-selection-input>
`
})
export class DebugRadioComponent {
formControl = new FormControl();
data: any[] = [];
selectedValue: any = null;
required = false;
constructor() {
this.formControl.valueChanges.subscribe(value => {
console.log('Radio selection value changed:', value);
});
this.formControl.statusChanges.subscribe(status => {
console.log('Form control status:', status);
});
}
onSelectionChange(selection: any) {
this.selectedValue = selection;
console.log('Selection changed:', selection);
}
}Performance Monitoring
@Component({
template: `
<div class="performance-info">
Data Size: {{ data?.length || 0 }} options<br>
Render Time: {{ renderTime }}ms<br>
Selection Operations: {{ selectionOperationCount }}
</div>
<app-radio-selection-input
[formControl]="formControl"
[data]="data"
(selectionChange)="onSelectionChange($event)">
</app-radio-selection-input>
`
})
export class PerformanceDebugComponent {
formControl = new FormControl();
data: any[] = [];
renderTime = 0;
selectionOperationCount = 0;
onSelectionChange(selection: any) {
const start = performance.now();
// Perform selection operation
this.processSelection(selection);
const end = performance.now();
this.renderTime = end - start;
this.selectionOperationCount++;
}
private processSelection(selection: any) {
// Your selection processing logic
}
}Accessibility Features
ARIA Support
- Radio buttons include proper ARIA roles and labels
- Group labeling through label input property
- Keyboard navigation is fully supported (Tab, Arrow keys, Space, Enter)
- Screen reader friendly with appropriate descriptions
- Focus management for keyboard users
- Proper radio button grouping
Keyboard Navigation
| Key | Action |
|-----|--------|
| Tab | Navigate to radio group |
| Arrow Keys | Navigate between radio options |
| Space | Select radio option |
| Enter | Select radio option |
Best Practices
- Provide meaningful labels for all radio options
- Use descriptive text for better accessibility
- Consider grouping related radio options
- Test with screen readers to ensure proper announcements
- Maintain logical tab order for keyboard navigation
- Use consistent styling for better visual accessibility
