list-selection-input 15.0.4 → 15.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,24 +1,1080 @@
1
- # ListSelectionInput
1
+ # List Selection Input Component
2
2
 
3
- This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.2.0.
3
+ ## Overview
4
4
 
5
- ## Code scaffolding
5
+ The `list-selection-input` library provides a Material Design dropdown selection component that allows users to select from a list of options in a space-efficient manner. It supports both single and multiple selection modes, validation limits, disabled items, and works with both string and object data formats. Built with Angular Material select components and implementing `ControlValueAccessor` for seamless form integration.
6
6
 
7
- Run `ng generate component component-name --project list-selection-input` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project list-selection-input`.
8
- > Note: Don't forget to add `--project list-selection-input` or else it will be added to the default project in your `angular.json` file.
7
+ ### Core Capabilities
9
8
 
10
- ## Build
9
+ #### 📋 Dropdown List Selection Interface
11
10
 
12
- Run `ng build list-selection-input` to build the project. The build artifacts will be stored in the `dist/` directory.
11
+ - **Single/Multiple Selection**: Support for both single selection and multi-select modes
12
+ - **Form Validation**: Built-in min/max selection validation with error handling
13
+ - **Flexible Data Support**: Handles both string arrays and complex object arrays
14
+ - **Disabled State Support**: Mark individual items as non-selectable
15
+ - **Material Design**: Built on Angular Material select foundation
16
+ - **Form Control Integration**: Implements `ControlValueAccessor` for reactive forms
17
+ - **Validation Integration**: Native Angular validation system compatibility
18
+ - **Space Efficient**: Dropdown interface saves screen space
13
19
 
14
- ## Publishing
20
+ #### 🔧 Features
15
21
 
16
- After building your library with `ng build list-selection-input`, go to the dist folder `cd dist/list-selection-input` and run `npm publish`.
22
+ **ControlValueAccessor Implementation** - Works with Angular forms
23
+ ✅ **Material Design Integration** - Uses Angular Material components
24
+ ✅ **Single & Multiple Selection** - Flexible selection modes
25
+ ✅ **Min/Max Validation** - Configurable selection limits
26
+ ✅ **Disabled Items** - Mark individual options as non-selectable
27
+ ✅ **Flexible Data Types** - Support for strings and objects
28
+ ✅ **Pre-selection** - Initialize with selected items
29
+ ✅ **Form Validation** - Native validation integration
30
+ ✅ **Dropdown Interface** - Space-efficient selection display
17
31
 
18
- ## Running unit tests
32
+ ### Key Benefits
19
33
 
20
- Run `ng test list-selection-input` to execute the unit tests via [Karma](https://karma-runner.github.io).
34
+ | Feature | Description |
35
+ |---------|-------------|
36
+ | **Space Efficient** | Compact dropdown interface vs. visible lists |
37
+ | **Flexible Selection** | Support for single or multiple item selection |
38
+ | **Rich Validation** | Built-in min/max selection limits with error states |
39
+ | **Data Format Support** | Works with simple strings or complex objects |
40
+ | **State Management** | Disabled states and pre-selection capabilities |
41
+ | **Form Integration** | Seamless Angular form control and validation integration |
21
42
 
22
- ## Further help
43
+ ---
23
44
 
24
- To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
45
+ ## Summary
46
+
47
+ The `list-selection-input` library provides a space-efficient dropdown selection component with comprehensive form integration, validation support, and multiple selection capabilities for Angular applications.
48
+
49
+ ---
50
+
51
+ ## Quick Start Guide
52
+
53
+ ### Installation & Setup (2 minutes)
54
+
55
+ #### 1. Import Module
56
+
57
+ ```typescript
58
+ // app.module.ts
59
+ import { ListSelectionInputModule } from 'list-selection-input';
60
+
61
+ @NgModule({
62
+ imports: [
63
+ ListSelectionInputModule
64
+ ]
65
+ })
66
+ export class AppModule { }
67
+ ```
68
+
69
+ #### 2. No Module Configuration Required
70
+
71
+ The `ListSelectionInputModule` does not require global configuration. Components can be used immediately after module import.
72
+
73
+ ### Quick Examples
74
+
75
+ #### Example 1: Basic Single Selection
76
+
77
+ ```typescript
78
+ import { Component } from '@angular/core';
79
+ import { FormControl } from '@angular/forms';
80
+
81
+ @Component({
82
+ selector: 'app-basic-list-select',
83
+ template: `
84
+ <app-list-selection-input
85
+ [formControl]="selectionControl"
86
+ [data]="options">
87
+ </app-list-selection-input>
88
+
89
+ <div>Selected: {{ selectionControl.value }}</div>
90
+ `
91
+ })
92
+ export class BasicListSelectComponent {
93
+ selectionControl = new FormControl();
94
+
95
+ options = ['Option 1', 'Option 2', 'Option 3', 'Option 4'];
96
+ }
97
+ ```
98
+
99
+ #### Example 2: Multiple Selection with Validation
100
+
101
+ ```typescript
102
+ import { Component } from '@angular/core';
103
+ import { FormControl, Validators } from '@angular/forms';
104
+
105
+ @Component({
106
+ selector: 'app-multi-list-select',
107
+ template: `
108
+ <app-list-selection-input
109
+ [formControl]="multiControl"
110
+ [data]="skills"
111
+ [multiple]="true"
112
+ [minSelection]="2"
113
+ [maxSelection]="4"
114
+ label="Select Skills"
115
+ placeholder="Choose your skills">
116
+ </app-list-selection-input>
117
+
118
+ <div class="errors" *ngIf="multiControl.errors && multiControl.touched">
119
+ <div *ngIf="multiControl.hasError('minRequired')">
120
+ Please select at least 2 skills
121
+ </div>
122
+ <div *ngIf="multiControl.hasError('maxExceeded')">
123
+ You can select maximum 4 skills
124
+ </div>
125
+ </div>
126
+ `,
127
+ styles: [`
128
+ .errors {
129
+ color: #f44336;
130
+ font-size: 0.875rem;
131
+ margin-top: 0.5rem;
132
+ }
133
+ `]
134
+ })
135
+ export class MultiListSelectComponent {
136
+ multiControl = new FormControl([], [
137
+ Validators.required,
138
+ Validators.minLength(2),
139
+ Validators.maxLength(4)
140
+ ]);
141
+
142
+ skills = [
143
+ 'JavaScript', 'TypeScript', 'Python', 'Java', 'C#', 'PHP', 'Ruby', 'Go',
144
+ 'Rust', 'Swift', 'Kotlin', 'Dart', 'Scala', 'Clojure', 'Elixir', 'Haskell'
145
+ ];
146
+ }
147
+ ```
148
+
149
+ #### Example 3: Object Data with Disabled Items
150
+
151
+ ```typescript
152
+ import { Component } from '@angular/core';
153
+ import { FormControl } from '@angular/forms';
154
+
155
+ @Component({
156
+ selector: 'app-object-list-select',
157
+ template: `
158
+ <app-list-selection-input
159
+ [formControl]="userControl"
160
+ [data]="userOptions"
161
+ label="Assign User"
162
+ placeholder="Select a user"
163
+ displayField="label">
164
+ </app-list-selection-input>
165
+
166
+ <div *ngIf="userControl.value" class="user-info">
167
+ <h4>{{ userControl.value.label }}</h4>
168
+ <p>{{ userControl.value.email }}</p>
169
+ <p>Role: {{ userControl.value.role }}</p>
170
+ </div>
171
+ `,
172
+ styles: [`
173
+ .user-info {
174
+ margin-top: 1rem;
175
+ padding: 1rem;
176
+ border: 1px solid #ddd;
177
+ border-radius: 4px;
178
+ background: #f9f9f9;
179
+ }
180
+ .user-info h4 {
181
+ margin: 0 0 0.5rem 0;
182
+ color: #333;
183
+ }
184
+ .user-info p {
185
+ margin: 0.25rem 0;
186
+ font-size: 0.9rem;
187
+ color: #666;
188
+ }
189
+ `]
190
+ })
191
+ export class ObjectListSelectComponent {
192
+ userControl = new FormControl();
193
+
194
+ userOptions = [
195
+ { value: 'user1', label: 'John Doe', email: 'john@example.com', role: 'Admin', disabled: false },
196
+ { value: 'user2', label: 'Jane Smith', email: 'jane@example.com', role: 'Editor', disabled: false },
197
+ { value: 'user3', label: 'Bob Johnson', email: 'bob@example.com', role: 'Viewer', disabled: true },
198
+ { value: 'user4', label: 'Alice Brown', email: 'alice@example.com', role: 'Editor', disabled: false },
199
+ { value: 'user5', label: 'Charlie Wilson', email: 'charlie@example.com', role: 'Admin', disabled: false }
200
+ ];
201
+ }
202
+ ```
203
+
204
+ #### Example 4: Multi-Select with Pre-selection
205
+
206
+ ```typescript
207
+ import { Component } from '@angular/core';
208
+ import { FormControl } from '@angular/forms';
209
+
210
+ @Component({
211
+ selector: 'app-pre-selected-list',
212
+ template: `
213
+ <app-list-selection-input
214
+ [formControl]="preSelectedControl"
215
+ [data]="preSelectedOptions"
216
+ [multiple]="true"
217
+ label="Favorite Languages"
218
+ placeholder="Select your favorite programming languages">
219
+ </app-list-selection-input>
220
+
221
+ <div class="selection-info">
222
+ Selected ({{ preSelectedControl.value?.length || 0 }}):
223
+ {{ preSelectedControl.value?.join(', ') || 'None' }}
224
+ </div>
225
+ `,
226
+ styles: [`
227
+ .selection-info {
228
+ margin-top: 1rem;
229
+ padding: 0.75rem;
230
+ background: #e8f5e8;
231
+ border: 1px solid #4caf50;
232
+ border-radius: 4px;
233
+ color: #2e7d32;
234
+ font-size: 0.9rem;
235
+ }
236
+ `]
237
+ })
238
+ export class PreSelectedListComponent {
239
+ preSelectedControl = new FormControl(['JavaScript', 'TypeScript']);
240
+
241
+ preSelectedOptions = [
242
+ { value: 'JavaScript', label: 'JavaScript', selected: true },
243
+ { value: 'TypeScript', label: 'TypeScript', selected: true },
244
+ { value: 'Python', label: 'Python', selected: false },
245
+ { value: 'Java', label: 'Java', selected: false },
246
+ { value: 'C#', label: 'C#', selected: false },
247
+ { value: 'PHP', label: 'PHP', selected: false },
248
+ { value: 'Ruby', label: 'Ruby', selected: false },
249
+ { value: 'Go', label: 'Go', selected: false }
250
+ ];
251
+ }
252
+ ```
253
+
254
+ #### Example 5: Form Integration with Dynamic Options
255
+
256
+ ```typescript
257
+ import { Component } from '@angular/core';
258
+ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
259
+
260
+ @Component({
261
+ selector: 'app-dynamic-list-form',
262
+ template: `
263
+ <form [formGroup]="dynamicForm">
264
+ <app-list-selection-input
265
+ formControlName="category"
266
+ [data]="categoryOptions"
267
+ label="Product Category"
268
+ placeholder="Select a category"
269
+ [required]="true">
270
+ </app-list-selection-input>
271
+
272
+ <app-list-selection-input
273
+ formControlName="subcategories"
274
+ [data]="subcategoryOptions"
275
+ label="Subcategories"
276
+ placeholder="Select subcategories"
277
+ [multiple]="true"
278
+ [minSelection]="1"
279
+ [maxSelection]="3"
280
+ [disabled]="!dynamicForm.get('category')?.value">
281
+ </app-list-selection-input>
282
+ </form>
283
+
284
+ <div class="form-status">
285
+ <div>Form Valid: {{ dynamicForm.valid }}</div>
286
+ <div>Category: {{ dynamicForm.get('category')?.value }}</div>
287
+ <div>Subcategories: {{ dynamicForm.get('subcategories')?.value?.length || 0 }} selected</div>
288
+ </div>
289
+
290
+ <div class="controls">
291
+ <button (click)="resetForm()">Reset</button>
292
+ <button (click)="setDefaults()">Set Defaults</button>
293
+ <button (click)="submitForm()" [disabled]="dynamicForm.invalid">Submit</button>
294
+ </div>
295
+ `,
296
+ styles: [`
297
+ form {
298
+ display: flex;
299
+ flex-direction: column;
300
+ gap: 1rem;
301
+ max-width: 400px;
302
+ }
303
+ .form-status {
304
+ margin-top: 1rem;
305
+ padding: 1rem;
306
+ background: #f5f5f5;
307
+ border-radius: 4px;
308
+ font-family: monospace;
309
+ font-size: 0.9rem;
310
+ }
311
+ .controls {
312
+ margin-top: 1rem;
313
+ display: flex;
314
+ gap: 0.5rem;
315
+ }
316
+ button {
317
+ padding: 0.5rem 1rem;
318
+ border: 1px solid #ccc;
319
+ border-radius: 4px;
320
+ background: white;
321
+ cursor: pointer;
322
+ }
323
+ button:disabled {
324
+ opacity: 0.5;
325
+ cursor: not-allowed;
326
+ }
327
+ `]
328
+ })
329
+ export class DynamicListFormComponent {
330
+ dynamicForm: FormGroup;
331
+
332
+ categoryOptions = [
333
+ 'Electronics', 'Clothing', 'Books', 'Home & Garden', 'Sports & Outdoors',
334
+ 'Automotive', 'Health & Beauty', 'Toys & Games', 'Food & Beverages'
335
+ ];
336
+
337
+ subcategoryOptions: string[] = [];
338
+
339
+ constructor(private fb: FormBuilder) {
340
+ this.dynamicForm = this.fb.group({
341
+ category: ['', Validators.required],
342
+ subcategories: [[], [Validators.required, Validators.minLength(1), Validators.maxLength(3)]]
343
+ });
344
+
345
+ // Watch for category changes to update subcategories
346
+ this.dynamicForm.get('category')?.valueChanges.subscribe(category => {
347
+ this.updateSubcategories(category);
348
+ this.dynamicForm.get('subcategories')?.reset();
349
+ });
350
+ }
351
+
352
+ updateSubcategories(category: string) {
353
+ const subcategoryMap: { [key: string]: string[] } = {
354
+ 'Electronics': ['Smartphones', 'Laptops', 'Tablets', 'Headphones', 'Cameras'],
355
+ 'Clothing': ['Men\'s Wear', 'Women\'s Wear', 'Kids\' Wear', 'Accessories', 'Footwear'],
356
+ 'Books': ['Fiction', 'Non-Fiction', 'Educational', 'Children', 'Comics'],
357
+ 'Home & Garden': ['Furniture', 'Decor', 'Kitchen', 'Tools', 'Plants'],
358
+ 'Sports & Outdoors': ['Exercise', 'Outdoor Gear', 'Sports Equipment', 'Team Sports', 'Water Sports']
359
+ };
360
+
361
+ this.subcategoryOptions = subcategoryMap[category] || [];
362
+ }
363
+
364
+ resetForm() {
365
+ this.dynamicForm.reset();
366
+ this.subcategoryOptions = [];
367
+ }
368
+
369
+ setDefaults() {
370
+ this.dynamicForm.patchValue({
371
+ category: 'Electronics',
372
+ subcategories: ['Smartphones', 'Laptops']
373
+ });
374
+ }
375
+
376
+ submitForm() {
377
+ if (this.dynamicForm.valid) {
378
+ console.log('Form submitted:', this.dynamicForm.value);
379
+ alert('Form submitted successfully!');
380
+ }
381
+ }
382
+ }
383
+ ```
384
+
385
+ ---
386
+
387
+ ## Component API
388
+
389
+ ### Inputs
390
+
391
+ | Input | Type | Description | Default |
392
+ | :--- | :--- | :--- | :--- |
393
+ | `data` | `any[] \| string[]` | Array of options to select from | (Required) |
394
+ | `multiple` | `boolean` | Allow multiple selections | `false` |
395
+ | `label` | `string` | Label text for the select field | `undefined` |
396
+ | `placeholder` | `string` | Placeholder text for the select field | `undefined` |
397
+ | `displayField` | `string` | Property name to display for object arrays | `undefined` |
398
+ | `minSelection` | `number` | Minimum number of selections required | `0` |
399
+ | `maxSelection` | `number` | Maximum number of selections allowed | `0` |
400
+ | `required` | `boolean` | Whether selection is required | `false` |
401
+ | `disabled` | `boolean` | Whether the select is disabled | `false` |
402
+
403
+ ### Outputs
404
+
405
+ | Output | Type | Description |
406
+ |--------|------|-------------|
407
+ | `selectionChange` | `EventEmitter<any[]>` | Emits array of selected values when selection changes |
408
+
409
+ ---
410
+
411
+ ## Form Integration (ControlValueAccessor)
412
+
413
+ The component implements Angular's `ControlValueAccessor` interface for seamless form integration.
414
+
415
+ ### ControlValueAccessor Implementation
416
+
417
+ ```typescript
418
+ // writeValue(value: any[]): void
419
+ // Sets the selected values
420
+ writeValue(value: any[]): void {
421
+ this.selectedValues = value || [];
422
+ }
423
+
424
+ // registerOnChange(fn: any): void
425
+ // Registers a callback for value changes
426
+ registerOnChange(fn: any): void {
427
+ this.onChange = fn;
428
+ }
429
+
430
+ // registerOnTouched(fn: any): void
431
+ // Registers a callback for touch events
432
+ registerOnTouched(fn: any): void {
433
+ this.onTouch = fn;
434
+ }
435
+
436
+ // setDisabledState(isDisabled: boolean): void
437
+ // Sets disabled state
438
+ setDisabledState?(isDisabled: boolean): void {
439
+ this.disabled = isDisabled;
440
+ }
441
+ ```
442
+
443
+ ### Form Integration Examples
444
+
445
+ #### Reactive Forms
446
+
447
+ ```typescript
448
+ import { Component } from '@angular/core';
449
+ import { FormControl, FormGroup, Validators } from '@angular/forms';
450
+
451
+ @Component({
452
+ selector: 'app-reactive-list-form',
453
+ template: `
454
+ <form [formGroup]="listForm">
455
+ <app-list-selection-input
456
+ formControlName="country"
457
+ [data]="countries"
458
+ label="Country"
459
+ placeholder="Select a country"
460
+ [required]="true">
461
+ </app-list-selection-input>
462
+
463
+ <app-list-selection-input
464
+ formControlName="languages"
465
+ [data]="languages"
466
+ [multiple]="true"
467
+ label="Programming Languages"
468
+ placeholder="Select languages"
469
+ [minSelection]="1"
470
+ [maxSelection]="5">
471
+ </app-list-selection-input>
472
+ </form>
473
+ `
474
+ })
475
+ export class ReactiveListFormComponent {
476
+ listForm = new FormGroup({
477
+ country: new FormControl('', Validators.required),
478
+ languages: new FormControl([], [Validators.required, Validators.minLength(1), Validators.maxLength(5)])
479
+ });
480
+
481
+ countries = ['United States', 'Canada', 'United Kingdom', 'Germany', 'France', 'Japan'];
482
+ languages = ['JavaScript', 'Python', 'Java', 'C#', 'PHP', 'Ruby', 'Go', 'Rust'];
483
+ }
484
+ ```
485
+
486
+ #### Template-Driven Forms
487
+
488
+ ```typescript
489
+ import { Component } from '@angular/core';
490
+
491
+ @Component({
492
+ selector: 'app-template-list-form',
493
+ template: `
494
+ <app-list-selection-input
495
+ [(ngModel)]="selectedValue"
496
+ name="templateSelection"
497
+ [data]="templateOptions"
498
+ label="Template Selection"
499
+ placeholder="Choose an option"
500
+ [multiple]="true"
501
+ required>
502
+ </app-list-selection-input>
503
+
504
+ <div *ngIf="selectedValue">
505
+ Selected: {{ selectedValue.join(', ') }}
506
+ </div>
507
+ `
508
+ })
509
+ export class TemplateListFormComponent {
510
+ selectedValue: string[] = [];
511
+
512
+ templateOptions = ['Option A', 'Option B', 'Option C', 'Option D', 'Option E'];
513
+ }
514
+ ```
515
+
516
+ ---
517
+
518
+ ## Model Structures
519
+
520
+ ### Selection Option Interface
521
+
522
+ ```typescript
523
+ export interface SelectionOptionInterface {
524
+ value: any; // The value to be selected/returned
525
+ label?: string; // Optional display label
526
+ disabled?: boolean; // Whether this option is disabled
527
+ selected?: boolean; // Whether this option is pre-selected
528
+ }
529
+ ```
530
+
531
+ ### Selection Option Class
532
+
533
+ ```typescript
534
+ export class SelectionOption implements SelectionOptionInterface {
535
+ constructor(
536
+ public value: any,
537
+ public label?: string,
538
+ public disabled?: boolean = false,
539
+ public selected?: boolean = false,
540
+ ) {}
541
+
542
+ static adapt(item?: any): SelectionOption {
543
+ return new SelectionOption(
544
+ item?.value ?? item,
545
+ item?.label,
546
+ item?.disabled || false,
547
+ item?.selected || false,
548
+ );
549
+ }
550
+ }
551
+ ```
552
+
553
+ ### Usage Examples
554
+
555
+ ```typescript
556
+ // String array data (automatically converted)
557
+ const stringData = ['Option 1', 'Option 2', 'Option 3'];
558
+
559
+ // Object array data
560
+ const objectData = [
561
+ { value: 'opt1', label: 'Option 1', disabled: false, selected: true },
562
+ { value: 'opt2', label: 'Option 2', disabled: true, selected: false },
563
+ { value: 'opt3', label: 'Option 3', disabled: false, selected: false }
564
+ ];
565
+
566
+ // Manual SelectionOption creation
567
+ const manualOptions = [
568
+ new SelectionOption('value1', 'Label 1', false, true),
569
+ new SelectionOption('value2', 'Label 2', true, false),
570
+ new SelectionOption('value3', 'Label 3', false, false)
571
+ ];
572
+
573
+ // Using adapt method for flexible data
574
+ const adaptedOptions = [
575
+ SelectionOption.adapt({ value: 'item1', label: 'Item 1', selected: true }),
576
+ SelectionOption.adapt({ value: 'item2', label: 'Item 2', disabled: true }),
577
+ SelectionOption.adapt('item3') // String fallback
578
+ ];
579
+ ```
580
+
581
+ ---
582
+
583
+ ## Validation System
584
+
585
+ ### Built-in Validation
586
+
587
+ The component provides built-in validation for:
588
+
589
+ #### Min Selection Validation
590
+ ```typescript
591
+ // Requires at least X selections
592
+ [minSelection]="2" // Must select at least 2 items
593
+ ```
594
+
595
+ #### Max Selection Validation
596
+ ```typescript
597
+ // Allows maximum X selections
598
+ [maxSelection]="3" // Cannot select more than 3 items
599
+ ```
600
+
601
+ #### Required Validation
602
+ ```typescript
603
+ // Marks field as required
604
+ [required]="true" // At least one selection required
605
+ ```
606
+
607
+ ### Validation Error Types
608
+
609
+ | Error Type | Condition | Description |
610
+ |------------|-----------|-------------|
611
+ | `minRequired` | `selectedCount < minSelection` | Not enough selections made |
612
+ | `maxExceeded` | `selectedCount >= maxSelection` | Too many selections made |
613
+ | `required` | Control has required validator and no selections | Control is required but empty |
614
+
615
+ ### Custom Validation Examples
616
+
617
+ ```typescript
618
+ // Complex validation scenarios
619
+ const complexForm = new FormGroup({
620
+ permissions: new FormControl([], [
621
+ Validators.required,
622
+ Validators.minLength(1), // At least 1 selection
623
+ Validators.maxLength(5), // Maximum 5 selections
624
+ customPermissionValidator() // Custom validation logic
625
+ ])
626
+ });
627
+
628
+ // Custom validator example
629
+ function customPermissionValidator() {
630
+ return (control: AbstractControl): ValidationErrors | null => {
631
+ const value = control.value;
632
+ if (value && value.includes('admin') && value.includes('delete')) {
633
+ return { adminDeleteConflict: true };
634
+ }
635
+ return null;
636
+ };
637
+ }
638
+ ```
639
+
640
+ ---
641
+
642
+ ## Module Configuration
643
+
644
+ ### ListSelectionInputModule
645
+
646
+ **No Global Configuration Required**
647
+
648
+ The `ListSelectionInputModule` does not provide a `forRoot()` method or global configuration options. All configuration is done at the component level through input properties.
649
+
650
+ #### Dependencies
651
+
652
+ - **@angular/core**: Core Angular functionality
653
+ - **@angular/forms**: Form control integration (FormsModule, ReactiveFormsModule)
654
+ - **@angular/material**: Material Design components (MatSelectModule, MatFormFieldModule, etc.)
655
+
656
+ ---
657
+
658
+ ## Advanced Usage Patterns
659
+
660
+ ### Cascading Selections
661
+
662
+ ```typescript
663
+ import { Component } from '@angular/core';
664
+ import { FormBuilder, FormGroup } from '@angular/forms';
665
+
666
+ @Component({
667
+ selector: 'app-cascading-selects',
668
+ template: `
669
+ <form [formGroup]="cascadingForm">
670
+ <app-list-selection-input
671
+ formControlName="country"
672
+ [data]="countries"
673
+ label="Country"
674
+ placeholder="Select a country">
675
+ </app-list-selection-input>
676
+
677
+ <app-list-selection-input
678
+ formControlName="state"
679
+ [data]="states"
680
+ label="State/Province"
681
+ placeholder="Select a state"
682
+ [disabled]="!cascadingForm.get('country')?.value">
683
+ </app-list-selection-input>
684
+
685
+ <app-list-selection-input
686
+ formControlName="city"
687
+ [data]="cities"
688
+ label="City"
689
+ placeholder="Select a city"
690
+ [disabled]="!cascadingForm.get('state')?.value">
691
+ </app-list-selection-input>
692
+ </form>
693
+ `
694
+ })
695
+ export class CascadingSelectsComponent {
696
+ cascadingForm: FormGroup;
697
+ states: string[] = [];
698
+ cities: string[] = [];
699
+
700
+ countries = ['United States', 'Canada', 'United Kingdom'];
701
+
702
+ stateData = {
703
+ 'United States': ['California', 'New York', 'Texas', 'Florida'],
704
+ 'Canada': ['Ontario', 'Quebec', 'British Columbia', 'Alberta'],
705
+ 'United Kingdom': ['England', 'Scotland', 'Wales', 'Northern Ireland']
706
+ };
707
+
708
+ cityData = {
709
+ 'California': ['Los Angeles', 'San Francisco', 'San Diego', 'Sacramento'],
710
+ 'New York': ['New York City', 'Buffalo', 'Rochester', 'Albany'],
711
+ 'Ontario': ['Toronto', 'Ottawa', 'Mississauga', 'Hamilton']
712
+ };
713
+
714
+ constructor(private fb: FormBuilder) {
715
+ this.cascadingForm = this.fb.group({
716
+ country: [''],
717
+ state: [''],
718
+ city: ['']
719
+ });
720
+
721
+ // Watch for country changes
722
+ this.cascadingForm.get('country')?.valueChanges.subscribe(country => {
723
+ this.states = this.stateData[country as keyof typeof this.stateData] || [];
724
+ this.cascadingForm.get('state')?.reset();
725
+ this.cascadingForm.get('city')?.reset();
726
+ });
727
+
728
+ // Watch for state changes
729
+ this.cascadingForm.get('state')?.valueChanges.subscribe(state => {
730
+ this.cities = this.cityData[state as keyof typeof this.cityData] || [];
731
+ this.cascadingForm.get('city')?.reset();
732
+ });
733
+ }
734
+ }
735
+ ```
736
+
737
+ ### Async Data Loading
738
+
739
+ ```typescript
740
+ import { Component } from '@angular/core';
741
+ import { HttpClient } from '@angular/common/http';
742
+ import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
743
+ import { FormControl } from '@angular/forms';
744
+ import { Observable } from 'rxjs';
745
+
746
+ @Component({
747
+ selector: 'app-async-list-select',
748
+ template: `
749
+ <app-list-selection-input
750
+ [formControl]="searchControl"
751
+ [data]="searchResults$ | async"
752
+ label="Search Products"
753
+ placeholder="Start typing to search..."
754
+ displayField="name"
755
+ [loading]="loading">
756
+ </app-list-selection-input>
757
+
758
+ <div *ngIf="loading" class="loading-indicator">
759
+ Loading products...
760
+ </div>
761
+ `
762
+ })
763
+ export class AsyncListSelectComponent {
764
+ searchControl = new FormControl();
765
+ searchResults$!: Observable<any[]>;
766
+ loading = false;
767
+
768
+ constructor(private http: HttpClient) {
769
+ this.searchResults$ = this.searchControl.valueChanges.pipe(
770
+ debounceTime(300),
771
+ distinctUntilChanged(),
772
+ switchMap(query => this.searchProducts(query))
773
+ );
774
+ }
775
+
776
+ private searchProducts(query: string): Observable<any[]> {
777
+ this.loading = true;
778
+ return this.http.get<any[]>(`/api/products/search?q=${encodeURIComponent(query)}`).pipe(
779
+ // Simulate loading state
780
+ switchMap(results => {
781
+ this.loading = false;
782
+ return [results];
783
+ })
784
+ );
785
+ }
786
+ }
787
+ ```
788
+
789
+ ### Performance Optimization
790
+
791
+ ```typescript
792
+ import { Component, ChangeDetectionStrategy } from '@angular/core';
793
+ import { FormControl } from '@angular/forms';
794
+
795
+ @Component({
796
+ selector: 'app-optimized-list-select',
797
+ changeDetection: ChangeDetectionStrategy.OnPush,
798
+ template: `
799
+ <app-list-selection-input
800
+ [formControl]="optimizedControl"
801
+ [data]="largeDataset"
802
+ label="Large Dataset Selection"
803
+ placeholder="Select from large dataset..."
804
+ [filterFunction]="customFilter">
805
+ </app-list-selection-input>
806
+ `
807
+ })
808
+ export class OptimizedListSelectComponent {
809
+ optimizedControl = new FormControl();
810
+
811
+ // Large dataset with efficient filtering
812
+ largeDataset = Array.from({ length: 10000 }, (_, i) => ({
813
+ id: i,
814
+ name: `Product ${i}`,
815
+ category: `Category ${Math.floor(i / 100)}`,
816
+ price: Math.random() * 1000
817
+ }));
818
+
819
+ customFilter(data: any[], filterText: string): any[] {
820
+ if (!filterText) return data;
821
+
822
+ const lowerFilter = filterText.toLowerCase();
823
+ return data.filter(item =>
824
+ item.name.toLowerCase().includes(lowerFilter) ||
825
+ item.category.toLowerCase().includes(lowerFilter)
826
+ );
827
+ }
828
+ }
829
+ ```
830
+
831
+ ---
832
+
833
+ ## Integration Examples
834
+
835
+ ### With Other UI Components
836
+
837
+ ```typescript
838
+ import { Component } from '@angular/core';
839
+
840
+ @Component({
841
+ selector: 'app-integrated-list-select',
842
+ template: `
843
+ <app-display-card title="User Assignment">
844
+ <app-list-selection-input
845
+ [formControl]="userControl"
846
+ [data]="users"
847
+ label="Assign to User"
848
+ placeholder="Select a user"
849
+ displayField="name">
850
+ </app-list-selection-input>
851
+
852
+ <div *ngIf="userControl.value" class="assignment-info">
853
+ <h4>Assigned to: {{ userControl.value.name }}</h4>
854
+ <p>{{ userControl.value.email }}</p>
855
+ <p>Role: {{ userControl.value.role }}</p>
856
+ <p>Department: {{ userControl.value.department }}</p>
857
+ </div>
858
+ </app-display-card>
859
+ `
860
+ })
861
+ export class IntegratedListSelectComponent {
862
+ userControl = new FormControl();
863
+
864
+ users = [
865
+ {
866
+ id: 1,
867
+ name: 'John Doe',
868
+ email: 'john@example.com',
869
+ role: 'Admin',
870
+ department: 'Engineering'
871
+ },
872
+ {
873
+ id: 2,
874
+ name: 'Jane Smith',
875
+ email: 'jane@example.com',
876
+ role: 'Editor',
877
+ department: 'Marketing'
878
+ }
879
+ ];
880
+ }
881
+ ```
882
+
883
+ ### With State Management
884
+
885
+ ```typescript
886
+ import { Component } from '@angular/core';
887
+ import { Store } from '@ngrx/store';
888
+
889
+ @Component({
890
+ selector: 'app-state-list-select',
891
+ template: `
892
+ <app-list-selection-input
893
+ [formControl]="categoryControl"
894
+ [data]="categories$ | async"
895
+ label="Product Category"
896
+ placeholder="Select a category"
897
+ (selectionChange)="onCategorySelected($event)">
898
+ </app-list-selection-input>
899
+ `
900
+ })
901
+ export class StateListSelectComponent {
902
+ categoryControl = new FormControl();
903
+ categories$ = this.store.select(state => state.categories);
904
+
905
+ constructor(private store: Store) {}
906
+
907
+ onCategorySelected(category: any) {
908
+ this.store.dispatch(selectCategory({ category }));
909
+ }
910
+ }
911
+ ```
912
+
913
+ ---
914
+
915
+ ## Testing
916
+
917
+ ### Unit Testing Example
918
+
919
+ ```typescript
920
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
921
+ import { ListSelectionInputComponent } from './list-selection-input.component';
922
+ import { ReactiveFormsModule } from '@angular/forms';
923
+
924
+ describe('ListSelectionInputComponent', () => {
925
+ let component: ListSelectionInputComponent;
926
+ let fixture: ComponentFixture<ListSelectionInputComponent>;
927
+
928
+ beforeEach(async () => {
929
+ await TestBed.configureTestingModule({
930
+ declarations: [ ListSelectionInputComponent ],
931
+ imports: [ ReactiveFormsModule ]
932
+ }).compileComponents();
933
+
934
+ fixture = TestBed.createComponent(ListSelectionInputComponent);
935
+ component = fixture.componentInstance;
936
+ });
937
+
938
+ it('should create', () => {
939
+ expect(component).toBeTruthy();
940
+ });
941
+
942
+ it('should display options from data input', () => {
943
+ component.data = ['Option 1', 'Option 2', 'Option 3'];
944
+ fixture.detectChanges();
945
+
946
+ const compiled = fixture.nativeElement;
947
+ expect(compiled.textContent).toContain('Option 1');
948
+ expect(compiled.textContent).toContain('Option 2');
949
+ expect(compiled.textContent).toContain('Option 3');
950
+ });
951
+
952
+ it('should handle multiple selection mode', () => {
953
+ component.multiple = true;
954
+ component.data = ['Option 1', 'Option 2', 'Option 3'];
955
+
956
+ expect(component.multiple).toBe(true);
957
+ });
958
+
959
+ it('should emit selection changes', () => {
960
+ spyOn(component.selectionChange, 'emit');
961
+
962
+ component.onSelectionChange(['Option 1', 'Option 2']);
963
+
964
+ expect(component.selectionChange.emit).toHaveBeenCalledWith(['Option 1', 'Option 2']);
965
+ });
966
+
967
+ it('should validate minimum selections', () => {
968
+ component.minSelection = 2;
969
+ component.data = ['Option 1', 'Option 2', 'Option 3'];
970
+
971
+ // Simulate selecting only 1 item
972
+ component.writeValue(['Option 1']);
973
+
974
+ const validationResult = component.validate({} as AbstractControl);
975
+ expect(validationResult?.minRequired).toBe(true);
976
+ });
977
+ });
978
+ ```
979
+
980
+ ---
981
+
982
+ ## Troubleshooting
983
+
984
+ ### Common Issues
985
+
986
+ 1. **Form control not working**: Ensure ReactiveFormsModule is imported
987
+ 2. **Validation not triggering**: Check that validators are properly configured
988
+ 3. **Selection not updating**: Verify data format matches expected structure
989
+ 4. **Multiple selection not working**: Check that `multiple` input is set to `true`
990
+ 5. **Object display issues**: Set `displayField` property for object arrays
991
+ 6. **Styling issues**: Ensure Material theme is properly configured
992
+
993
+ ### Debug Mode
994
+
995
+ ```typescript
996
+ @Component({
997
+ template: `
998
+ <div class="debug-info">
999
+ Form Control Value: {{ formControl.value | json }}<br>
1000
+ Form Control Valid: {{ formControl.valid }}<br>
1001
+ Form Control Errors: {{ formControl.errors | json }}<br>
1002
+ Data Length: {{ data?.length || 0 }}<br>
1003
+ Multiple Mode: {{ multiple }}<br>
1004
+ Min Selection: {{ minSelection }}<br>
1005
+ Max Selection: {{ maxSelection }}
1006
+ </div>
1007
+
1008
+ <app-list-selection-input
1009
+ [formControl]="formControl"
1010
+ [data]="data"
1011
+ [multiple]="multiple"
1012
+ [minSelection]="minSelection"
1013
+ [maxSelection]="maxSelection"
1014
+ (selectionChange)="onSelectionChange($event)">
1015
+ </app-list-selection-input>
1016
+ `
1017
+ })
1018
+ export class DebugListSelectComponent {
1019
+ formControl = new FormControl();
1020
+ data: any[] = [];
1021
+ multiple = false;
1022
+ minSelection = 0;
1023
+ maxSelection = 0;
1024
+
1025
+ constructor() {
1026
+ this.formControl.valueChanges.subscribe(value => {
1027
+ console.log('List selection value changed:', value);
1028
+ });
1029
+
1030
+ this.formControl.statusChanges.subscribe(status => {
1031
+ console.log('Form control status:', status);
1032
+ });
1033
+ }
1034
+
1035
+ onSelectionChange(selection: any[]) {
1036
+ console.log('Selection changed:', selection);
1037
+ }
1038
+ }
1039
+ ```
1040
+
1041
+ ### Performance Debugging
1042
+
1043
+ ```typescript
1044
+ @Component({
1045
+ template: `
1046
+ <div class="performance-info">
1047
+ Data Size: {{ data?.length || 0 }} items<br>
1048
+ Render Time: {{ renderTime }}ms<br>
1049
+ Filter Operations: {{ filterOperationCount }}
1050
+ </div>
1051
+
1052
+ <app-list-selection-input
1053
+ [formControl]="formControl"
1054
+ [data]="data"
1055
+ (selectionChange)="onSelectionChange($event)">
1056
+ </app-list-selection-input>
1057
+ `
1058
+ })
1059
+ export class PerformanceDebugComponent {
1060
+ formControl = new FormControl();
1061
+ data: any[] = [];
1062
+ renderTime = 0;
1063
+ filterOperationCount = 0;
1064
+
1065
+ onSelectionChange(selection: any[]) {
1066
+ const start = performance.now();
1067
+
1068
+ // Perform selection operation
1069
+ this.processSelection(selection);
1070
+
1071
+ const end = performance.now();
1072
+ this.renderTime = end - start;
1073
+ this.filterOperationCount++;
1074
+ }
1075
+
1076
+ private processSelection(selection: any[]) {
1077
+ // Your selection processing logic
1078
+ }
1079
+ }
1080
+ ```