filter-selection-input
v15.0.5
Published
This is an Angular Module containing Components/Services using Material
Readme
Filter Selection Input Component
Overview
The filter-selection-input library provides a Material Design autocomplete selection component that allows users to select from a filtered list of options. It features real-time filtering as users type, force selection from provided options, and support for both strings and objects. Built with Angular Material and implementing ControlValueAccessor for seamless form integration.
Core Capabilities
🔍 Autocomplete Selection Interface
- Real-time Filtering: Filters options as users type in the input field
- Force Selection: Ensures users select from provided options only
- Single Selection: Only allows selection from the filtered list (like radio buttons)
- Flexible Data Support: Handles both string arrays and complex objects
- Form Control Integration: Implements
ControlValueAccessorfor reactive forms - Material Design: Built on Angular Material autocomplete foundation
- Customizable Display: Configurable labels and placeholders
🔧 Features
✅ Real-time Filtering - Instant filtering as user types
✅ Force Selection - User must select from provided options
✅ Single Selection - Radio button-like exclusive selection
✅ Material Design Integration - Uses Angular Material components
✅ Form Control Support - ControlValueAccessor implementation
✅ Flexible Data Types - Support for strings and objects
✅ Customizable Labels - Configurable display labels
✅ Placeholder Support - Helpful placeholder text
Key Benefits
| Feature | Description | |---------|-------------| | Type-Ahead Search | Users can quickly find options by typing | | Data Validation | Ensures selections are from the allowed options | | Space Efficient | Compact dropdown interface | | Form Integration | Seamless Angular form control integration | | User Friendly | Intuitive autocomplete experience |
Summary
The filter-selection-input library provides a type-ahead selection component that combines the benefits of autocomplete search with controlled selection options, making it ideal for large option lists where users need to find and select specific items.
Quick Start Guide
Installation & Setup (2 minutes)
1. Import Module
// app.module.ts
import { FilterSelectionInputModule } from 'filter-selection-input';
@NgModule({
imports: [
FilterSelectionInputModule
]
})
export class AppModule { }2. No Module Configuration Required
The FilterSelectionInputModule 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-filter',
template: `
<app-filter-selection-input
[formControl]="countryControl"
[data]="countries"
label="Select Country"
placeholder="Start typing to search...">
</app-filter-selection-input>
<div>Selected: {{ countryControl.value }}</div>
`
})
export class BasicFilterComponent {
countryControl = new FormControl();
countries = [
'United States', 'Canada', 'United Kingdom', 'Germany', 'France',
'Italy', 'Spain', 'Australia', 'Japan', 'China', 'India', 'Brazil',
'Mexico', 'Netherlands', 'Sweden', 'Norway', 'Denmark', 'Finland',
'Switzerland', 'Austria', 'Belgium', 'Ireland', 'Portugal', 'Greece'
];
}Example 2: Object Array Selection
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-object-filter',
template: `
<app-filter-selection-input
[formControl]="userControl"
[data]="users"
label="Select User"
placeholder="Search users by name or email..."
displayField="label">
</app-filter-selection-input>
<div *ngIf="userControl.value">
Selected User: {{ userControl.value.label }} ({{ userControl.value.email }})
</div>
`
})
export class ObjectFilterComponent {
userControl = new FormControl();
users = [
{ id: 1, label: 'John Doe', email: '[email protected]', department: 'Engineering' },
{ id: 2, label: 'Jane Smith', email: '[email protected]', department: 'Marketing' },
{ id: 3, label: 'Bob Johnson', email: '[email protected]', department: 'Sales' },
{ id: 4, label: 'Alice Brown', email: '[email protected]', department: 'HR' },
{ id: 5, label: 'Charlie Wilson', email: '[email protected]', department: 'Engineering' },
{ id: 6, label: 'Diana Davis', email: '[email protected]', department: 'Finance' },
{ id: 7, label: 'Edward Miller', email: '[email protected]', department: 'Operations' },
{ id: 8, label: 'Fiona Garcia', email: '[email protected]', department: 'Marketing' }
];
}Example 3: Required Selection with Validation
import { Component } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-required-filter',
template: `
<app-filter-selection-input
[formControl]="categoryControl"
[data]="categories"
label="Product Category"
placeholder="Search categories..."
[required]="true">
</app-filter-selection-input>
<div class="errors" *ngIf="categoryControl.errors && categoryControl.touched">
<div *ngIf="categoryControl.hasError('required')">
Category selection is required
</div>
</div>
<div>Selected Category: {{ categoryControl.value }}</div>
`
})
export class RequiredFilterComponent {
categoryControl = new FormControl('', Validators.required);
categories = [
'Electronics', 'Clothing', 'Books', 'Home & Garden', 'Sports & Outdoors',
'Automotive', 'Health & Beauty', 'Toys & Games', 'Food & Beverages',
'Office Supplies', 'Art & Collectibles', 'Musical Instruments',
'Pet Supplies', 'Baby Products', 'Tools & Hardware'
];
}Component API
Inputs
| Input | Type | Description | Default |
| :--- | :--- | :--- | :--- |
| data | any[] \| string[] | Array of options to filter and select from | (Required) |
| label | string | Label text for the input field | undefined |
| placeholder | string | Placeholder text for the input field | 'Start typing to search...' |
| displayField | string | Property name to display for object arrays | undefined |
| required | boolean | Whether selection is required | false |
| disabled | boolean | Whether the input is disabled | false |
Outputs
| Output | Type | Description |
|--------|------|-------------|
| filterChange | EventEmitter<string> | Emits when filter text changes |
| selectionChange | EventEmitter<any> | Emits 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;
}Module Configuration
FilterSelectionInputModule
No Global Configuration Required
The FilterSelectionInputModule 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
- @angular/material: Material Design components
Advanced Usage Patterns
Custom Filtering Logic
import { Component } from '@angular/core';
@Component({
selector: 'app-custom-filtering',
template: `
<app-filter-selection-input
[formControl]="customControl"
[data]="complexData"
label="Search Products"
placeholder="Search by name, category, or SKU..."
displayField="displayName">
</app-filter-selection-input>
`
})
export class CustomFilteringComponent {
customControl = new FormControl();
complexData = [
{
id: 1,
name: 'iPhone 15 Pro',
category: 'Electronics',
sku: 'IPH15P-256-BLU',
displayName: 'iPhone 15 Pro 256GB Blue'
},
{
id: 2,
name: 'MacBook Air M2',
category: 'Computers',
sku: 'MBA-M2-512-SLV',
displayName: 'MacBook Air M2 512GB Silver'
}
];
}Async Data Loading
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-async-filter',
template: `
<app-filter-selection-input
[formControl]="asyncControl"
[data]="asyncOptions"
label="Search Users"
placeholder="Search users by name or email..."
displayField="displayName"
[loading]="loading"
(filterChange)="onFilterChange($event)">
</app-filter-selection-input>
<div *ngIf="loading" class="loading-indicator">
Loading options...
</div>
`
})
export class AsyncFilterComponent {
asyncControl = new FormControl();
asyncOptions: any[] = [];
loading = false;
debounceTimer: any;
constructor(private http: HttpClient) {}
onFilterChange(filterText: string) {
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => {
this.loadOptions(filterText);
}, 300);
}
private loadOptions(filterText: string) {
this.loading = true;
this.http.get<any[]>(`/api/users/search?q=${encodeURIComponent(filterText)}`)
.subscribe({
next: (options) => {
this.asyncOptions = options;
this.loading = false;
},
error: (error) => {
console.error('Error loading options:', error);
this.loading = false;
}
});
}
}Integration Examples
With Other UI Components
import { Component } from '@angular/core';
@Component({
selector: 'app-integrated-filter',
template: `
<app-display-card title="User Selection">
<app-filter-selection-input
[formControl]="userControl"
[data]="users"
label="Select User"
placeholder="Search users..."
displayField="name">
</app-filter-selection-input>
<div *ngIf="userControl.value" class="user-details">
<h4>{{ userControl.value.name }}</h4>
<p>{{ userControl.value.email }}</p>
<p>Department: {{ userControl.value.department }}</p>
</div>
</app-display-card>
`
})
export class IntegratedFilterComponent {
userControl = new FormControl();
users = [
{ id: 1, name: 'John Doe', email: '[email protected]', department: 'Engineering' },
{ id: 2, name: 'Jane Smith', email: '[email protected]', department: 'Marketing' }
];
}Testing
Unit Testing Example
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FilterSelectionInputComponent } from './filter-selection-input.component';
import { ReactiveFormsModule } from '@angular/forms';
describe('FilterSelectionInputComponent', () => {
let component: FilterSelectionInputComponent;
let fixture: ComponentFixture<FilterSelectionInputComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FilterSelectionInputComponent ],
imports: [ ReactiveFormsModule ]
}).compileComponents();
fixture = TestBed.createComponent(FilterSelectionInputComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should filter data based on input', () => {
component.data = ['Apple', 'Banana', 'Cherry', 'Date'];
component.filterText = 'Ban';
fixture.detectChanges();
expect(component.filteredData).toEqual(['Banana']);
});
it('should emit selection changes', () => {
spyOn(component.selectionChange, 'emit');
component.onSelectionChange('Apple');
expect(component.selectionChange.emit).toHaveBeenCalledWith('Apple');
});
});Troubleshooting
Common Issues
- Filtering not working: Ensure data array is properly formatted
- Selection not updating: Check form control integration
- Object display issues: Set displayField property for object arrays
- Styling issues: Verify Material theme is properly configured
Debug Mode
@Component({
template: `
<div class="debug-info">
Filter Text: {{ filterText }}<br>
Filtered Data Length: {{ filteredData?.length || 0 }}<br>
Selected Value: {{ formControl?.value | json }}<br>
Form Control Valid: {{ formControl?.valid }}
</div>
<app-filter-selection-input
[formControl]="formControl"
[data]="data"
(filterChange)="onFilterChange($event)"
(selectionChange)="onSelectionChange($event)">
</app-filter-selection-input>
`
})
export class DebugFilterComponent {
formControl = new FormControl();
data: any[] = [];
filterText = '';
onFilterChange(filter: string) {
this.filterText = filter;
console.log('Filter changed:', filter);
}
onSelectionChange(selection: any) {
console.log('Selection changed:', selection);
}
}