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

range-selection-input

v15.0.3

Published

This is an Angular Module containing Components/Services using Material

Readme

Range Selection Input Component

Overview

The range-selection-input library provides a Material Design slider component that allows users to select numeric values or ranges through an intuitive draggable interface. It supports both single value sliders and dual-thumb range selection, configurable min/max values, step divisions, tick marks, and display formatting. Built with Angular Material slider components and implementing ControlValueAccessor for seamless form integration.

Core Capabilities

🎚️ Numeric Range Selection Interface

  • Single Value Selection: Select one numeric value using a single thumb slider
  • Dual Range Selection: Select a range using dual thumbs with minimum and maximum values
  • Configurable Range: Set minimum, maximum, and step values
  • Tick Mark Support: Optional tick marks for discrete value selection
  • Display Formatting: Customizable value display with formatting functions
  • Form Control Integration: Implements ControlValueAccessor for reactive forms
  • Validation Support: Native Angular validation system compatibility
  • Material Design: Built on Angular Material slider foundation

🔧 Features

ControlValueAccessor Implementation - Works with Angular forms
Material Design Integration - Uses Angular Material components
Single & Range Selection - Flexible selection modes
Configurable Steps - Set increment/decrement values
Tick Marks - Discrete value selection support
Value Formatting - Custom display formatting
Form Validation - Native validation integration
Disabled State - Disable slider interaction
Display Functions - Custom value presentation

Key Benefits

| Feature | Description | |---------|-------------| | Intuitive Selection | Drag-based numeric value selection | | Range Support | Select minimum and maximum values simultaneously | | Precise Control | Configurable step values for precise selection | | Visual Feedback | Tick marks and value display | | Form Integration | Seamless Angular form control integration | | Flexible Formatting | Custom value display formatting |


Summary

The range-selection-input library provides an intuitive numeric selection component with support for both single values and ranges, making it ideal for settings like price ranges, quantity selections, or any scenario requiring precise numeric input through a visual interface.


Quick Start Guide

Installation & Setup (2 minutes)

1. Import Module

// app.module.ts
import { RangeSelectionInputModule } from 'range-selection-input';

@NgModule({
  imports: [
    RangeSelectionInputModule
  ]
})
export class AppModule { }

2. No Module Configuration Required

The RangeSelectionInputModule does not require global configuration. Components can be used immediately after module import.

Quick Examples

Example 1: Basic Single Value Slider

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-basic-range',
  template: `
    <app-range-selection-input
      [formControl]="volumeControl"
      [min]="0"
      [max]="100"
      [step]="1"
      label="Volume Level"
      [showTicks]="true">
    </app-range-selection-input>
    
    <div>Volume: {{ volumeControl.value }}</div>
  `
})
export class BasicRangeComponent {
  volumeControl = new FormControl(50);
}

Example 2: Dual Range Slider (Price Range)

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-price-range',
  template: `
    <app-range-selection-input
      [formControl]="priceControl"
      [min]="0"
      [max]="1000"
      [step]="10"
      [showTicks]="true"
      label="Price Range ($)"
      [displayFunction]="formatPrice">
    </app-range-selection-input>
    
    <div class="price-display">
      <div *ngIf="priceControl.value">
        <strong>Selected Range:</strong> 
        {{ formatPrice(priceControl.value.min) }} - {{ formatPrice(priceControl.value.max) }}
      </div>
    </div>
  `,
  styles: [`
    .price-display {
      margin-top: 1rem;
      padding: 1rem;
      background: #f5f5f5;
      border-radius: 4px;
      font-size: 1.1rem;
    }
  `]
})
export class PriceRangeComponent {
  priceControl = new FormControl({ min: 100, max: 500 });
  
  formatPrice(value: number): string {
    return `$${value.toFixed(0)}`;
  }
}

Example 3: Quantity Selection with Validation

import { Component } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-quantity-selector',
  template: `
    <app-range-selection-input
      [formControl]="quantityControl"
      [min]="1"
      [max]="10"
      [step]="1"
      [showTicks]="true"
      label="Select Quantity"
      [required]="true">
    </app-range-selection-input>
    
    <div class="errors" *ngIf="quantityControl.errors && quantityControl.touched">
      <div *ngIf="quantityControl.hasError('required')">
        Quantity selection is required
      </div>
      <div *ngIf="quantityControl.hasError('min')">
        Minimum quantity is 1
      </div>
      <div *ngIf="quantityControl.hasError('max')">
        Maximum quantity is 10
      </div>
    </div>
    
    <div class="quantity-info">
      Quantity: {{ quantityControl.value }}
      <span *ngIf="quantityControl.value">
        (Total: ${{ (quantityControl.value * unitPrice).toFixed(2) }})
      </span>
    </div>
  `,
  styles: [`
    .errors {
      color: #f44336;
      font-size: 0.875rem;
      margin-top: 0.5rem;
    }
    .quantity-info {
      margin-top: 1rem;
      padding: 0.75rem;
      background: #e8f5e8;
      border: 1px solid #4caf50;
      border-radius: 4px;
      color: #2e7d32;
    }
  `]
})
export class QuantitySelectorComponent {
  quantityControl = new FormControl(1, [
    Validators.required,
    Validators.min(1),
    Validators.max(10)
  ]);
  
  unitPrice = 29.99;
}

Example 4: Percentage Selection

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-percentage-selector',
  template: `
    <app-range-selection-input
      [formControl]="opacityControl"
      [min]="0"
      [max]="100"
      [step]="5"
      [showTicks]="true"
      label="Opacity Level"
      [displayFunction]="formatPercentage">
    </app-range-selection-input>
    
    <div class="preview-container">
      <div 
        class="preview-box" 
        [style.opacity]="(opacityControl.value || 50) / 100">
        <p>Preview Box</p>
        <p>Opacity: {{ formatPercentage(opacityControl.value || 50) }}</p>
      </div>
    </div>
  `,
  styles: [`
    .preview-container {
      margin-top: 2rem;
      display: flex;
      justify-content: center;
    }
    .preview-box {
      width: 200px;
      height: 100px;
      background: linear-gradient(45deg, #2196f3, #21cbf3);
      border-radius: 8px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      color: white;
      font-weight: bold;
      text-shadow: 0 1px 2px rgba(0,0,0,0.5);
      transition: opacity 0.3s ease;
    }
    .preview-box p {
      margin: 0.25rem 0;
    }
  `]
})
export class PercentageSelectorComponent {
  opacityControl = new FormControl(75);
  
  formatPercentage(value: number): string {
    return `${value}%`;
  }
}

Example 5: Temperature Range Selection

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-temperature-range',
  template: `
    <app-range-selection-input
      [formControl]="temperatureControl"
      [min]="-10"
      [max]="40"
      [step]="1"
      [showTicks]="true"
      label="Temperature Range (°C)"
      [displayFunction]="formatTemperature">
    </app-range-selection-input>
    
    <div class="temperature-info" [attr.data-range]="getTemperatureRange()">
      <h4>Temperature Range</h4>
      <p>{{ formatTemperature(temperatureControl.value.min) }} to {{ formatTemperature(temperatureControl.value.max) }}</p>
      <div class="range-description">
        {{ getTemperatureDescription() }}
      </div>
    </div>
  `,
  styles: [`
    .temperature-info {
      margin-top: 1rem;
      padding: 1rem;
      border-radius: 8px;
      text-align: center;
      transition: all 0.3s ease;
    }
    .temperature-info[data-range="cold"] {
      background: linear-gradient(135deg, #e3f2fd, #bbdefb);
      border: 2px solid #2196f3;
      color: #1565c0;
    }
    .temperature-info[data-range="moderate"] {
      background: linear-gradient(135deg, #fff3e0, #ffcc02);
      border: 2px solid #ff9800;
      color: #ef6c00;
    }
    .temperature-info[data-range="warm"] {
      background: linear-gradient(135deg, #ffebee, #ef5350);
      border: 2px solid #f44336;
      color: #c62828;
    }
    .range-description {
      margin-top: 0.5rem;
      font-style: italic;
      opacity: 0.8;
    }
  `]
})
export class TemperatureRangeComponent {
  temperatureControl = new FormControl({ min: 15, max: 25 });
  
  formatTemperature(value: number): string {
    if (value < 0) {
      return `${Math.abs(value)}°C below freezing`;
    }
    return `${value}°C`;
  }
  
  getTemperatureRange(): string {
    const avg = (this.temperatureControl.value.min + this.temperatureControl.value.max) / 2;
    if (avg < 10) return 'cold';
    if (avg < 25) return 'moderate';
    return 'warm';
  }
  
  getTemperatureDescription(): string {
    const range = this.getTemperatureRange();
    const descriptions = {
      cold: 'Cool weather - jacket recommended',
      moderate: 'Pleasant temperature',
      warm: 'Warm weather - stay hydrated'
    };
    return descriptions[range];
  }
}

Example 6: Form Integration with Dynamic Range

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-dynamic-range-form',
  template: `
    <form [formGroup]="dynamicForm">
      <app-range-selection-input
        formControlName="age"
        [min]="18"
        [max]="100"
        [step]="1"
        [showTicks]="true"
        label="Age"
        placeholder="Select age"
        [required]="true">
      </app-range-selection-input>
      
      <app-range-selection-input
        formControlName="budget"
        [min]="budgetRange.min"
        [max]="budgetRange.max"
        [step]="budgetRange.step"
        [showTicks]="true"
        label="Budget Range ($)"
        [displayFunction]="formatCurrency">
      </app-range-selection-input>
      
      <app-range-selection-input
        formControlName="rating"
        [min]="0"
        [max]="5"
        [step]="0.5"
        [showTicks]="true"
        label="Rating"
        [displayFunction]="formatStars">
      </app-range-selection-input>
    </form>
    
    <div class="form-status">
      <div>Form Valid: {{ dynamicForm.valid }}</div>
      <div>Age: {{ dynamicForm.get('age')?.value }}</div>
      <div>Budget: {{ formatCurrency(dynamicForm.get('budget')?.value?.min) }} - {{ formatCurrency(dynamicForm.get('budget')?.value?.max) }}</div>
      <div>Rating: {{ formatStars(dynamicForm.get('rating')?.value) }}</div>
    </div>
    
    <div class="controls">
      <button (click)="setDefaults()">Set Defaults</button>
      <button (click)="resetForm()">Reset</button>
      <button (click)="submitForm()" [disabled]="dynamicForm.invalid">Submit</button>
    </div>
  `,
  styles: [`
    form {
      display: flex;
      flex-direction: column;
      gap: 2rem;
      max-width: 400px;
    }
    .form-status {
      margin-top: 1rem;
      padding: 1rem;
      background: #f5f5f5;
      border-radius: 4px;
      font-family: monospace;
      font-size: 0.9rem;
    }
    .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 DynamicRangeFormComponent {
  dynamicForm: FormGroup;
  budgetRange = { min: 1000, max: 10000, step: 100 };
  
  constructor(private fb: FormBuilder) {
    this.dynamicForm = this.fb.group({
      age: [25, [Validators.required, Validators.min(18), Validators.max(100)]],
      budget: [{ min: 3000, max: 7000 }, [Validators.required]],
      rating: [3.5, [Validators.required, Validators.min(0), Validators.max(5)]]
    });
  }

  formatCurrency(value: number): string {
    if (typeof value === 'object' && value !== null) {
      return `$${value.min?.toLocaleString()} - $${value.max?.toLocaleString()}`;
    }
    return `$${value.toLocaleString()}`;
  }

  formatStars(value: number): string {
    const fullStars = Math.floor(value);
    const hasHalfStar = value % 1 >= 0.5;
    const emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
    
    return '★'.repeat(fullStars) + 
           (hasHalfStar ? '☆' : '') + 
           '☆'.repeat(emptyStars) + 
           ` (${value})`;
  }

  setDefaults() {
    this.dynamicForm.patchValue({
      age: 30,
      budget: { min: 2500, max: 8000 },
      rating: 4.0
    });
  }

  resetForm() {
    this.dynamicForm.reset();
  }

  submitForm() {
    if (this.dynamicForm.valid) {
      console.log('Form submitted:', this.dynamicForm.value);
      alert('Form submitted successfully!');
    }
  }
}

Component API

Inputs

| Input | Type | Description | Default | | :--- | :--- | :--- | :--- | | min | number | Minimum value for the slider | 0 | | max | number | Maximum value for the slider | 100 | | step | number | Step increment/decrement value | 1 | | showTicks | boolean | Whether to show tick marks | false | | label | string | Label text for the slider | undefined | | placeholder | string | Placeholder text for the slider | undefined | | displayFunction | (value: number) => string | Function to format display values | undefined | | required | boolean | Whether selection is required | false | | disabled | boolean | Whether the slider is disabled | false |

Outputs

| Output | Type | Description | |--------|------|-------------| | valueChange | EventEmitter<number \| { min: number; max: number }> | Emits when slider value changes |


Form Integration (ControlValueAccessor)

The component implements Angular's ControlValueAccessor interface for seamless form integration.

ControlValueAccessor Implementation

// writeValue(value: number | { min: number; max: number }): void
// Sets the slider value(s)
writeValue(value: number | { min: number; max: number }): 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-range-form',
  template: `
    <form [formGroup]="rangeForm">
      <app-range-selection-input
        formControlName="singleValue"
        [min]="0"
        [max]="100"
        [step]="5"
        [showTicks]="true"
        label="Single Value"
        [required]="true">
      </app-range-selection-input>
      
      <app-range-selection-input
        formControlName="rangeValue"
        [min]="0"
        [max]="1000"
        [step]="25"
        [showTicks]="true"
        label="Range Value"
        [required]="true">
      </app-range-selection-input>
    </form>
  `
})
export class ReactiveRangeFormComponent {
  rangeForm = new FormGroup({
    singleValue: new FormControl(50, [Validators.required, Validators.min(0), Validators.max(100)]),
    rangeValue: new FormControl({ min: 100, max: 500 }, [Validators.required])
  });
}

Template-Driven Forms

import { Component } from '@angular/core';

@Component({
  selector: 'app-template-range-form',
  template: `
    <app-range-selection-input
      [(ngModel)]="selectedValue"
      name="templateRange"
      [min]="0"
      [max]="100"
      [step]="10"
      [showTicks]="true"
      label="Template Range Selection"
      required>
    </app-range-selection-input>
    
    <div *ngIf="selectedValue">
      Selected: {{ selectedValue }}
    </div>
  `
})
export class TemplateRangeFormComponent {
  selectedValue = 25;
}

Module Configuration

RangeSelectionInputModule

No Global Configuration Required

The RangeSelectionInputModule 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 (MatSliderModule, MatFormFieldModule, etc.)

Advanced Usage Patterns

Custom Display Formatting

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-custom-formatting',
  template: `
    <app-range-selection-input
      [formControl]="progressControl"
      [min]="0"
      [max]="100"
      [step]="5"
      [showTicks]="true"
      label="Project Progress"
      [displayFunction]="formatProgress">
    </app-range-selection-input>
    
    <div class="progress-visual">
      <div class="progress-bar">
        <div 
          class="progress-fill" 
          [style.width.%]="progressControl.value || 0">
        </div>
      </div>
      <span class="progress-text">{{ formatProgress(progressControl.value || 0) }}</span>
    </div>
  `,
  styles: [`
    .progress-visual {
      margin-top: 2rem;
    }
    .progress-bar {
      width: 100%;
      height: 20px;
      background: #e0e0e0;
      border-radius: 10px;
      overflow: hidden;
      margin-bottom: 0.5rem;
    }
    .progress-fill {
      height: 100%;
      background: linear-gradient(90deg, #4caf50, #8bc34a);
      transition: width 0.3s ease;
    }
    .progress-text {
      font-weight: bold;
      color: #333;
    }
  `]
})
export class CustomFormattingComponent {
  progressControl = new FormControl(25);
  
  formatProgress(value: number): string {
    const percentage = Math.round(value);
    const status = percentage < 25 ? 'Just Started' : 
                 percentage < 50 ? 'Early Progress' :
                 percentage < 75 ? 'Halfway There' :
                 percentage < 90 ? 'Almost Done' : 'Complete';
    return `${percentage}% - ${status}`;
  }
}

Dynamic Range Updates

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-dynamic-range',
  template: `
    <app-range-selection-input
      [formControl]="difficultyControl"
      [min]="difficultyRange.min"
      [max]="difficultyRange.max"
      [step]="difficultyRange.step"
      [showTicks]="true"
      label="Difficulty Level"
      [displayFunction]="formatDifficulty">
    </app-range-selection-input>
    
    <div class="difficulty-info">
      <h4>Difficulty: {{ formatDifficulty(difficultyControl.value || difficultyRange.min) }}</h4>
      <p>{{ getDifficultyDescription() }}</p>
    </div>
    
    <div class="range-controls">
      <label>
        Quick Presets:
        <select (change)="setPreset($event.target.value)">
          <option value="">Choose preset</option>
          <option value="easy">Easy (1-3)</option>
          <option value="medium">Medium (4-6)</option>
          <option value="hard">Hard (7-9)</option>
        </select>
      </label>
    </div>
  `,
  styles: [`
    .difficulty-info {
      margin-top: 1rem;
      padding: 1rem;
      border-radius: 8px;
      text-align: center;
    }
    .difficulty-info[data-level="easy"] {
      background: #e8f5e8;
      border: 2px solid #4caf50;
      color: #2e7d32;
    }
    .difficulty-info[data-level="medium"] {
      background: #fff3e0;
      border: 2px solid #ff9800;
      color: #ef6c00;
    }
    .difficulty-info[data-level="hard"] {
      background: #ffebee;
      border: 2px solid #f44336;
      color: #c62828;
    }
    .range-controls {
      margin-top: 1rem;
    }
    .range-controls select {
      margin-left: 0.5rem;
      padding: 0.25rem;
    }
  `]
})
export class DynamicRangeComponent {
  difficultyControl = new FormControl(5);
  difficultyRange = { min: 1, max: 10, step: 1 };
  
  formatDifficulty(value: number): string {
    if (value <= 3) return `Easy (${value})`;
    if (value <= 6) return `Medium (${value})`;
    return `Hard (${value})`;
  }
  
  getDifficultyDescription(): string {
    const value = this.difficultyControl.value || this.difficultyRange.min;
    if (value <= 3) return 'Simple and straightforward tasks';
    if (value <= 6) return 'Moderate complexity with some challenges';
    return 'Complex tasks requiring significant effort';
  }
  
  setPreset(preset: string) {
    const presets = {
      easy: { min: 1, max: 3 },
      medium: { min: 4, max: 6 },
      hard: { min: 7, max: 9 }
    };
    
    if (preset && presets[preset as keyof typeof presets]) {
      const range = presets[preset as keyof typeof presets];
      this.difficultyControl.setValue(range.min);
      this.difficultyRange = { ...this.difficultyRange, ...range };
    }
  }
}

Performance Optimization

import { Component, ChangeDetectionStrategy } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-optimized-range',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <app-range-selection-input
      [formControl]="optimizedControl"
      [min]="0"
      [max]="1000000"
      [step]="1000"
      [showTicks]="true"
      label="Large Range Selection"
      [displayFunction]="formatLargeNumber">
    </app-range-selection-input>
  `
})
export class OptimizedRangeComponent {
  optimizedControl = new FormControl(500000);
  
  formatLargeNumber(value: number): string {
    if (value >= 1000000) {
      return `${(value / 1000000).toFixed(1)}M`;
    } else if (value >= 1000) {
      return `${(value / 1000).toFixed(0)}K`;
    }
    return value.toString();
  }
}

Integration Examples

With Other UI Components

import { Component } from '@angular/core';

@Component({
  selector: 'app-integrated-range',
  template: `
    <app-display-card title="Price Configuration">
      <app-range-selection-input
        [formControl]="priceControl"
        [min]="0"
        [max]="1000"
        [step]="10"
        [showTicks]="true"
        label="Price Range"
        [displayFunction]="formatPrice">
      </app-range-selection-input>
      
      <div *ngIf="priceControl.value" class="price-summary">
        <h4>Price Configuration</h4>
        <p><strong>Selected Range:</strong> {{ formatPrice(priceControl.value.min) }} - {{ formatPrice(priceControl.value.max) }}</p>
        <p><strong>Average Price:</strong> {{ formatPrice((priceControl.value.min + priceControl.value.max) / 2) }}</p>
        <p><strong>Price Spread:</strong> {{ formatPrice(priceControl.value.max - priceControl.value.min) }}</p>
      </div>
    </app-display-card>
  `
})
export class IntegratedRangeComponent {
  priceControl = new FormControl({ min: 100, max: 500 });
  
  formatPrice(value: number): string {
    if (typeof value === 'object' && value !== null) {
      return `$${value.min?.toFixed(0)} - $${value.max?.toFixed(0)}`;
    }
    return `$${value.toFixed(0)}`;
  }
}

With State Management

import { Component } from '@angular/core';
import { Store } from '@ngrx/store';

@Component({
  selector: 'app-state-range',
  template: `
    <app-range-selection-input
      [formControl]="volumeControl"
      [data]="volumeOptions$ | async"
      label="Audio Volume"
      placeholder="Adjust volume"
      (valueChange)="onVolumeChange($event)">
    </app-range-selection-input>
  `
})
export class StateRangeComponent {
  volumeControl = new FormControl();
  volumeOptions$ = this.store.select(state => state.volumeOptions);
  
  constructor(private store: Store) {}
  
  onVolumeChange(volume: number) {
    this.store.dispatch(adjustVolume({ volume }));
  }
}

Testing

Unit Testing Example

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RangeSelectionInputComponent } from './range-selection-input.component';
import { ReactiveFormsModule } from '@angular/forms';

describe('RangeSelectionInputComponent', () => {
  let component: RangeSelectionInputComponent;
  let fixture: ComponentFixture<RangeSelectionInputComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ RangeSelectionInputComponent ],
      imports: [ ReactiveFormsModule ]
    }).compileComponents();

    fixture = TestBed.createComponent(RangeSelectionInputComponent);
    component = fixture.componentInstance;
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should handle single value selection', () => {
    component.min = 0;
    component.max = 100;
    component.step = 1;
    fixture.detectChanges();
    
    component.writeValue(50);
    expect(component.selectedValue).toBe(50);
  });

  it('should handle range value selection', () => {
    component.min = 0;
    component.max = 100;
    component.step = 1;
    fixture.detectChanges();
    
    component.writeValue({ min: 25, max: 75 });
    expect(component.selectedValue).toEqual({ min: 25, max: 75 });
  });

  it('should emit value changes', () => {
    spyOn(component.valueChange, 'emit');
    
    component.onValueChange(50);
    
    expect(component.valueChange.emit).toHaveBeenCalledWith(50);
  });

  it('should handle form control integration', () => {
    const formControl = new FormControl();
    component.formControl = formControl;
    
    spyOn(formControl, 'setValue');
    component.writeValue(75);
    
    expect(formControl.setValue).toHaveBeenCalledWith(75);
  });

  it('should apply custom display formatting', () => {
    component.displayFunction = (value: number) => `${value}%`;
    fixture.detectChanges();
    
    const formattedValue = component.displayFunction(50);
    expect(formattedValue).toBe('50%');
  });
});

Troubleshooting

Common Issues

  1. Form control not working: Ensure ReactiveFormsModule is imported
  2. Slider not responding: Check min/max/step values are properly configured
  3. Display formatting not working: Verify displayFunction is properly defined
  4. Range selection not working: Ensure dual-thumb mode is properly configured
  5. Styling issues: Verify Material theme is properly configured

Debug Mode

@Component({
  template: `
    <div class="debug-info">
      Form Control Value: {{ formControl.value | json }}<br>
      Form Control Valid: {{ formControl.valid }}<br>
      Min: {{ min }}<br>
      Max: {{ max }}<br>
      Step: {{ step }}<br>
      Show Ticks: {{ showTicks }}<br>
      Selected Value: {{ selectedValue | json }}
    </div>
    
    <app-range-selection-input
      [formControl]="formControl"
      [min]="min"
      [max]="max"
      [step]="step"
      [showTicks]="showTicks"
      [displayFunction]="displayFunction"
      (valueChange)="onValueChange($event)">
    </app-range-selection-input>
  `
})
export class DebugRangeComponent {
  formControl = new FormControl();
  min = 0;
  max = 100;
  step = 1;
  showTicks = false;
  selectedValue: any = null;
  
  displayFunction = (value: number) => `Value: ${value}`;
  
  constructor() {
    this.formControl.valueChanges.subscribe(value => {
      console.log('Range value changed:', value);
    });
    
    this.formControl.statusChanges.subscribe(status => {
      console.log('Form control status:', status);
    });
  }

  onValueChange(value: any) {
    this.selectedValue = value;
    console.log('Value changed:', value);
  }
}

Performance Monitoring

@Component({
  template: `
    <div class="performance-info">
      Range Size: {{ max - min }}<br>
      Step Count: {{ (max - min) / step }}<br>
      Update Time: {{ updateTime }}ms<br>
      Change Operations: {{ changeOperationCount }}
    </div>
    
    <app-range-selection-input
      [formControl]="formControl"
      [min]="min"
      [max]="max"
      [step]="step"
      (valueChange)="onValueChange($event)">
    </app-range-selection-input>
  `
})
export class PerformanceDebugComponent {
  formControl = new FormControl();
  min = 0;
  max = 1000;
  step = 1;
  updateTime = 0;
  changeOperationCount = 0;

  onValueChange(value: any) {
    const start = performance.now();
    
    // Perform value update operation
    this.processValueUpdate(value);
    
    const end = performance.now();
    this.updateTime = end - start;
    this.changeOperationCount++;
  }

  private processValueUpdate(value: any) {
    // Your value processing logic
  }
}

Accessibility Features

ARIA Support

  • Sliders include proper ARIA labels and roles
  • Keyboard navigation is fully supported (Arrow keys, Page Up/Down, Home/End)
  • Screen reader friendly with appropriate descriptions
  • Focus management for keyboard users
  • Value announcements for screen readers

Keyboard Navigation

| Key | Action | |-----|--------| | Arrow Keys | Adjust slider value by step | | Page Up/Down | Adjust by larger increments | | Home | Set to minimum value | | End | Set to maximum value | | Space | Toggle selection (if applicable) |

Best Practices

  1. Provide meaningful labels for slider accessibility
  2. Set appropriate min/max values for context
  3. Use sensible step values for precision
  4. Test with screen readers to ensure proper announcements
  5. Consider keyboard users for navigation
  6. Use consistent styling for better visual accessibility