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

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 ControlValueAccessor for 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 made

Custom 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

  1. Form control not working: Ensure ReactiveFormsModule is imported
  2. Selection not updating: Check that data format matches expected structure
  3. Object display issues: Set displayField property for object arrays
  4. Multiple selections: Radio buttons only support single selection by design
  5. Styling issues: Verify Material theme is properly configured
  6. 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

  1. Provide meaningful labels for all radio options
  2. Use descriptive text for better accessibility
  3. Consider grouping related radio options
  4. Test with screen readers to ensure proper announcements
  5. Maintain logical tab order for keyboard navigation
  6. Use consistent styling for better visual accessibility