PRD: Multi-Select Checkbox Component (Angular + Angular Material)
=================================================================
1. Overview
-----------
We need a reusable Angular Material component that enables users to select multiple items using checkboxes within a dropdown or similar interface. This component will be used to filter or select items (e.g., Line Items, Bid Zones, Suppliers) before performing an action like downloading a catalog.
2. Goals & Objectives
---------------------
1. **Usability**
* Provide a clear, intuitive UI using Angular Material patterns (e.g., `mat-select`, `mat-checkbox`).
2. **Reusability**
* Create a standalone component that can be plugged into different parts of the application with minimal configuration.
3. **Performance**
* Efficiently handle large lists of selectable items without significant UI lag.
4. **Consistency**
* Use Angular Material design principles to maintain a uniform look and feel across the app.
3. User Stories
---------------
1. **As a user**, I want to see a dropdown that lists multiple items, each with a checkbox, so I can select or deselect them.
2. **As a user**, I want a “Select All” option that quickly selects every item in the list.
3. **As a user**, I want a “Clear All” option that quickly deselects all items.
4. **As a user**, I want to see a count of how many items I’ve selected.
5. **As a user**, I want to confirm my selection and use it to filter or download specific data.
4. Functional Requirements
--------------------------
1. **Dropdown (mat-select) with Checkboxes**
* The component should display a list of items in a dropdown (or a popover) with each item having a checkbox.
* Use `[multiple]="true"` on `mat-select` to allow multi-selection.
2. **Select All / Deselect All**
* A special “Select All” option (checkbox or action) at the top of the list.
* Toggling “Select All” should select/deselect every item in the list.
3. **Display Selected Count**
* The trigger for the dropdown (the `mat-select-trigger`) should show the count of selected items (e.g., “3 selected”).
4. **Accessibility**
* Ensure keyboard navigation and screen-reader compatibility (ARIA labels, focus states, etc.).
5. **Integration**
* The component should emit an event (or use a reactive form control) that reflects the currently selected values so that parent components can react (e.g., enabling a “Download” button).
5. Non-Functional Requirements
------------------------------
1. **Performance**
* Must handle up to hundreds (or potentially thousands) of items without freezing the UI.
* Consider virtual scrolling or lazy loading if needed.
2. **Maintainability**
* Well-structured code with clear separation of component logic, styling, and tests.
3. **Scalability**
* Ability to extend to grouped or hierarchical options in the future.
6. UI/UX Design
---------------
A typical Angular Material form field containing a multi-select dropdown:
```
+------------------------------------------------------+
| Apply Filters To Download Catalog |
+------------------------------------------------------+
| [Select Line Items v] |
| - [ ] Select All |
| - [ ] 29TDHEX_ARIBA1_LONG NAME |
| - [ ] BAA OFF SAA OFF |
| - [ ] SMARTLINK LLC |
| - [ ] C0107 - Coaxial Cable System... |
| ... |
+------------------------------------------------------+
| [Select Bid Zones v] |
| - [ ] New York |
| - [ ] Chicago |
| - [ ] New Jersey |
| ... |
+------------------------------------------------------+
| [Select Suppliers v] |
| - [ ] Nokia |
| ... |
+------------------------------------------------------+
| [ Cancel ] [ Download ] |
+------------------------------------------------------+
```
* Each filter category uses the same multi-select component, just bound to different data arrays.
* The user can open each dropdown to select items.
* “Select All” and “Clear All” appear within the dropdown list or as part of the list.
7. Implementation Steps
-----------------------
### Step 1: Create the Multi-Select Component
**File**: `multi-select-dropdown.component.ts`
**Selector**: `<app-multi-select-dropdown>`
1. **Inputs**
```ts
@Input() placeholder: string = 'Select options';
@Input() options: { label: string; value: any }[] = [];
@Input() selectedValues: any[] = []; // optional, for default selection
```
* `options` is the list of selectable items.
* `selectedValues` is the list of pre-selected items (if any).
2. **Outputs**
```ts
@Output() selectionChange = new EventEmitter<any[]>();
```
3. **Template (HTML)**
```html
<mat-form-field appearance="fill">
<mat-label>{{ placeholder }}</mat-label>
<mat-select
[value]="selectedValues"
[multiple]="true"
(selectionChange)="onSelectionChange($event)">
<!-- Select All Option -->
<mat-option
#selectAllOption
(click)="toggleSelectAll($event)"
[disabled]="options.length === 0">
<mat-checkbox
[checked]="isAllSelected()"
(click)="$event.preventDefault()">
Select All
</mat-checkbox>
</mat-option>
<mat-divider></mat-divider>
<!-- Actual Options -->
<mat-option
*ngFor="let option of options"
[value]="option.value">
<mat-checkbox
[checked]="selectedValues.includes(option.value)"
(click)="$event.preventDefault()">
{{ option.label }}
</mat-checkbox>
</mat-option>
</mat-select>
<!-- Optionally display selected count in mat-select-trigger -->
<mat-select-trigger>
{{ selectedValues.length }} selected
</mat-select-trigger>
</mat-form-field>
```
4. **Component Logic (TypeScript)**
```ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { MatSelectChange } from '@angular/material/select';
@Component({
selector: 'app-multi-select-dropdown',
templateUrl: './multi-select-dropdown.component.html',
styleUrls: ['./multi-select-dropdown.component.scss']
})
export class MultiSelectDropdownComponent {
@Input() placeholder = 'Select options';
@Input() options: { label: string; value: any }[] = [];
@Input() selectedValues: any[] = [];
@Output() selectionChange = new EventEmitter<any[]>();
onSelectionChange(event: MatSelectChange) {
// event.value is an array of currently selected values
this.selectedValues = event.value;
this.selectionChange.emit(this.selectedValues);
}
isAllSelected(): boolean {
return this.selectedValues.length === this.options.length && this.options.length > 0;
}
toggleSelectAll(event: Event) {
event.stopPropagation();
if (this.isAllSelected()) {
// Clear all
this.selectedValues = [];
} else {
// Select all
this.selectedValues = this.options.map(opt => opt.value);
}
this.selectionChange.emit(this.selectedValues);
}
}
```
5. **Styles (SCSS)**
* Keep it minimal, relying mostly on Angular Material’s styling.
* Add spacing or custom styling as needed.
### Step 2: Integrate into the Filter Modal
**File**: `apply-filters-modal.component.ts`
1. **Data Sources**
```ts
lineItems = [
{ label: 'C0107 - Coaxial Cable System...', value: 'c0107' },
{ label: '29TDHEX_ARIBA1_LONG NAME', value: '29tdhex' },
// ...
];
bidZones = [
{ label: 'New York', value: 'ny' },
{ label: 'Chicago', value: 'chicago' },
// ...
];
suppliers = [
{ label: 'Nokia', value: 'nokia' },
// ...
];
```
2. **Template** (`apply-filters-modal.component.html`)
```html
<h2>Apply Filters To Download Catalog</h2>
<app-multi-select-dropdown
placeholder="Select Line Items"
[options]="lineItems"
(selectionChange)="onLineItemsChange($event)">
</app-multi-select-dropdown>
<app-multi-select-dropdown
placeholder="Select Bid Zones"
[options]="bidZones"
(selectionChange)="onBidZonesChange($event)">
</app-multi-select-dropdown>
<app-multi-select-dropdown
placeholder="Select Suppliers"
[options]="suppliers"
(selectionChange)="onSuppliersChange($event)">
</app-multi-select-dropdown>
<div class="actions">
<button mat-button (click)="onCancel()">Cancel</button>
<button mat-raised-button color="primary" (click)="onDownload()">Download</button>
</div>
```
3. **Logic** (`apply-filters-modal.component.ts`)
```ts
import { Component } from '@angular/core';
@Component({
selector: 'app-apply-filters-modal',
templateUrl: './apply-filters-modal.component.html',
styleUrls: ['./apply-filters-modal.component.scss']
})
export class ApplyFiltersModalComponent {
selectedLineItems: any[] = [];
selectedBidZones: any[] = [];
selectedSuppliers: any[] = [];
onLineItemsChange(selected: any[]) {
this.selectedLineItems = selected;
}
onBidZonesChange(selected: any[]) {
this.selectedBidZones = selected;
}
onSuppliersChange(selected: any[]) {
this.selectedSuppliers = selected;
}
onCancel() {
// Close modal or reset
}
onDownload() {
// Combine all selected arrays into filter criteria
const filterPayload = {
lineItems: this.selectedLineItems,
bidZones: this.selectedBidZones,
suppliers: this.selectedSuppliers
};
// Trigger the actual download logic (service call, etc.)
// e.g., this.catalogService.downloadFilteredCatalog(filterPayload);
}
}
```
### Step 3: Testing
1. **Unit Tests (Jasmine/Karma)**
* **Component Tests**:
* Check that “Select All” adds all items to `selectedValues`.
* Check that toggling an individual item updates `selectedValues` correctly.
* Check that the component emits `selectionChange` with the correct array.
2. **Integration Tests**
* In `apply-filters-modal.component.spec.ts`, verify that selecting/deselecting items updates the parent component’s state.
* Verify that clicking “Download” calls the appropriate service with the expected payload.
3. **E2E Tests (Protractor/Cypress)**
* Open the modal, select a few items, verify that “Download” uses the correct filters.
* Test the “Select All” and “Clear All” flow.
### Step 4: Deployment
* Once QA and UAT (User Acceptance Testing) are complete, integrate into the main application build.
* Ensure the new component is documented in the project’s UI component library or style guide.
* * *
8. Acceptance Criteria
----------------------
1. **UI**: Displays a multi-select dropdown using Angular Material, each option has a checkbox.
2. **Functionality**: “Select All” and “Deselect All” work as expected.
3. **State Management**: The parent component receives the correct list of selected values.
4. **Accessibility**: Keyboard and screen-reader navigation are supported via Angular Material.
5. **Performance**: Large option sets do not significantly degrade the user experience (consider lazy loading if necessary).
* * *
9. Timeline & Milestones
------------------------
1. **Design/Prototyping**: 1 week
2. **Development**: 1 week
3. **Integration & Testing**: 1 week
4. **UAT & QA**: 3-5 days
5. **Deployment**: After final approvals
* * *
10. Risks & Mitigation
----------------------
1. **Large Data Sets**
* Consider virtual scrolling or pagination if performance is impacted.
2. **Complex Filtering**
* If future requirements include hierarchical filters or advanced search, plan for an expandable component design.
3. **Browser Compatibility**
* Angular Material supports modern browsers; verify any required polyfills for older browsers if necessary.
* * *
### Conclusion
By implementing this **Multi-Select Checkbox** component with Angular Material, we ensure a consistent, accessible, and maintainable approach to multi-selection across the application. The outlined design and steps provide a clear roadmap for development, integration, and testing. Once approved, the team can proceed with the implementation and rollout.
* * *
**End of PRD**