Reputation: 165
I am using mat-select to display drop down, I need select all functionality in angular drop down.
Below is my html
<mat-select formControlName="myControl" multiple (ngModelChange)="resetSelect($event, 'myControl')">
<mat-option>Select All</mat-option>
<mat-option [value]="1">Option 1</mat-option>
<mat-option [value]="2">Option 2</mat-option>
<mat-option [value]="3">Option 3</mat-option>
</mat-select>
Here is my ts code
/**
* click handler that resets a multiple select
* @param {Array} $event current value of the field
* @param {string} field name of the formControl in the formGroup
*/
protected resetSelect($event: string[], field: string) {
// when resetting the value, this method gets called again, so stop recursion if we have no values
if ($event.length === 0) {
return;
}
// first option (with no value) has been clicked
if ($event[0] === undefined) {
// reset the formControl value
this.myFormGroup.get(field).setValue([]);
}
}
Some how its not working properly, kindly help or let me any better way to do this.
Upvotes: 7
Views: 16375
Reputation: 1453
You can create a directive to handle toggle all and other scenarios.
Demo: https://stackblitz.com/github/Angular-Material-Dev/mat-select-all
Below can be code for the directive
import {
AfterViewInit,
Directive,
Input,
OnDestroy,
inject,
} from '@angular/core';
import { MatOption, MatSelect } from '@angular/material/select';
import { Subscription } from 'rxjs';
@Directive({
selector: 'mat-option[selectAll]',
standalone: true,
})
export class SelectAllDirective implements AfterViewInit, OnDestroy {
@Input({ required: true }) allValues: any[] = [];
private _matSelect = inject(MatSelect);
private _matOption = inject(MatOption);
private _subscriptions: Subscription[] = [];
ngAfterViewInit(): void {
const parentSelect = this._matSelect;
const parentFormControl = parentSelect.ngControl.control;
// For changing other option selection based on select all
this._subscriptions.push(
this._matOption.onSelectionChange.subscribe((ev) => {
if (ev.isUserInput) {
if (ev.source.selected) {
parentFormControl?.setValue(this.allValues);
this._matOption.select(false);
} else {
parentFormControl?.setValue([]);
this._matOption.deselect(false);
}
}
})
);
// For changing select all based on other option selection
this._subscriptions.push(
parentSelect.optionSelectionChanges.subscribe((v) => {
if (v.isUserInput && v.source.value !== this._matOption.value) {
if (!v.source.selected) {
this._matOption.deselect(false);
} else {
if (parentFormControl?.value.length === this.allValues.length) {
this._matOption.select(false);
}
}
}
})
);
// If user has kept all values selected in select's form-control from the beginning
setTimeout(() => {
if (parentFormControl?.value.length === this.allValues.length) {
this._matOption.select(false);
}
});
}
ngOnDestroy(): void {
this._subscriptions.forEach((s) => s.unsubscribe());
}
}
For example, consider below template:
<mat-form-field>
<mat-label>Toppings</mat-label>
<mat-select [formControl]="toppings" multiple>
<mat-option value="select-all" selectAll [allValues]="toppingList"
>Select all</mat-option
>
@for (topping of toppingList; track topping) {
<mat-option [value]="topping">{{ topping }}</mat-option>
}
</mat-select>
</mat-form-field>
Notice usage of selectAll
directive
<mat-option value="select-all" selectAll [allValues]="toppingList"
>Select all</mat-option
>
And below could be class file
import { Component } from '@angular/core';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatFormFieldModule } from '@angular/material/form-field';
@Component({
selector: 'app-root',
standalone: true,
imports: [
MatFormFieldModule,
MatSelectModule,
FormsModule,
ReactiveFormsModule,
MatInputModule,
],
templateUrl: './app.component.html',
})
export class AppComponent {
toppingList: string[] = [
'Extra cheese',
'Mushroom',
'Onion',
'Pepperoni',
'Sausage',
'Tomato',
];
toppings = new FormControl<string[] | undefined>([]);
}
Upvotes: 0
Reputation: 27303
Use click event try this
<mat-form-field>
<mat-select placeholder="Toppings" [formControl]="toppings" multiple>
<mat-option [value]="1" (click)="selectAll(ev)"
#ev
>SelectAll</mat-option>
<mat-option *ngFor="let topping of toppingList" [value]="topping">{{topping}}</mat-option>
</mat-select>
</mat-form-field>
}
Typescript:
selectAll(ev) {
if(ev._selected) {
this.toppings.setValue(['Extra cheese', 'Mushroom', 'Onion', 'Pepperoni', 'Sausage', 'Tomato']);
ev._selected=true;
}
if(ev._selected==false) {
this.toppings.setValue([]);
}
}
Example:https://stackblitz.com/edit/angular-czmxfp
Upvotes: 9
Reputation: 56
I extended Angular Material's MatSelect to include following features:
Working example can be found here on StackBlitz and complete repository can be found here on GitHub
Following is the code snippet of "Select All" implementation
<button *ngIf="isGroup && values.length" mat-icon-button (click)="toggleGroup()">
<mat-icon>
{{ isCollapsed ? 'chevron_right' : 'expand_more' }}
</mat-icon>
</button>
<ng-container *ngIf="values.length" [ngSwitch]="isMultiple">
<mat-checkbox *ngSwitchCase="true" disableRipple matRipple class="mat-option" color="primary"
[ngClass]="{ 'mat-selected': isIndeterminate() || isChecked() }" [indeterminate]="isIndeterminate()"
[checked]="isChecked()" (click)="$event.stopPropagation()" (change)="toggleSelection($event)">
{{text}}
</mat-checkbox>
<span *ngSwitchDefault>
{{text}}
</span>
</ng-container>
import { Component, Input, ViewEncapsulation, OnChanges, SimpleChanges, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatCheckboxChange } from '@angular/material';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
// tslint:disable-next-line:component-selector
selector: 'mat-select-check',
templateUrl: './mat-select-check.component.html',
styleUrls: ['./mat-select-check.component.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MatSelectCheckComponent implements OnChanges, OnInit, OnDestroy {
@Input() selectControl: FormControl;
@Input() values = [];
@Input() text = 'Select All';
@Input() isGroup = false;
@Input() groupData: any;
@Input() dataKey = 'Key';
@Input() isMultiple?: boolean;
private _onDestroy = new Subject<void>();
isCollapsed = false;
constructor(private changeDetectorRef: ChangeDetectorRef) { }
ngOnChanges(changes: SimpleChanges) {
if (changes && changes.values && changes.values.currentValue && changes.values.currentValue.length) {
this.values = changes.values.currentValue.map(z => z[this.dataKey]);
}
setTimeout(() => {
this.changeDetectorRef.detectChanges();
});
}
ngOnInit() {
if (this.selectControl) {
this.selectControl.valueChanges
.pipe(takeUntil(this._onDestroy))
.subscribe(value => setTimeout(() => {
this.changeDetectorRef.detectChanges();
}));
}
}
ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
}
isChecked(): boolean {
const hasValues = this.selectControl.value && this.values.length && this.selectControl.value.length;
if (hasValues) {
const length = this.selectControl.value.filter(x => this.values.includes(x)).length;
return length > 0 && length === this.values.length;
}
return false;
}
isIndeterminate(): boolean {
const hasValues = this.selectControl.value && this.values.length && this.selectControl.value.length;
if (hasValues) {
const length = this.selectControl.value.filter(x => this.values.includes(x)).length;
return length > 0 && length < this.values.length;
}
return false;
}
toggleSelection(change: MatCheckboxChange): void {
if (change.checked) {
const newValue = this.selectControl.value ? [...this.selectControl.value, ...this.values] : [...this.values];
this.selectControl.setValue(Array.from(new Set(newValue)));
} else {
this.selectControl.setValue(this.selectControl.value.filter(x => !this.values.includes(x)));
}
}
toggleGroup() {
this.isCollapsed = !this.isCollapsed;
this.groupData[this.groupData.findIndex(x => x.key === this.text)].isVisible = !this.isCollapsed;
}
}
Upvotes: 1
Reputation: 61
Here is an example of how to extend a material option component.
See stackblitz Demo with usage example
Component:
import { ChangeDetectorRef, Component, ElementRef, HostListener, HostBinding, Inject, Input, OnDestroy, OnInit, Optional } from '@angular/core';
import { MAT_OPTION_PARENT_COMPONENT, MatOptgroup, MatOption, MatOptionParentComponent } from '@angular/material/core';
import { AbstractControl } from '@angular/forms';
import { MatPseudoCheckboxState } from '@angular/material/core/selection/pseudo-checkbox/pseudo-checkbox';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-select-all-option',
templateUrl: './select-all-option.component.html',
styleUrls: ['./select-all-option.component.css']
})
export class SelectAllOptionComponent extends MatOption implements OnInit, OnDestroy {
protected unsubscribe: Subject<any>;
@Input() control: AbstractControl;
@Input() title: string;
@Input() values: any[] = [];
@HostBinding('class') cssClass = 'mat-option';
@HostListener('click') toggleSelection(): void {
this. _selectViaInteraction();
this.control.setValue(this.selected ? this.values : []);
}
constructor(elementRef: ElementRef<HTMLElement>,
changeDetectorRef: ChangeDetectorRef,
@Optional() @Inject(MAT_OPTION_PARENT_COMPONENT) parent: MatOptionParentComponent,
@Optional() group: MatOptgroup) {
super(elementRef, changeDetectorRef, parent, group);
this.title = 'Select All';
}
ngOnInit(): void {
this.unsubscribe = new Subject<any>();
this.refresh();
this.control.valueChanges
.pipe(takeUntil(this.unsubscribe))
.subscribe(() => {
this.refresh();
});
}
ngOnDestroy(): void {
super.ngOnDestroy();
this.unsubscribe.next();
this.unsubscribe.complete();
}
get selectedItemsCount(): number {
return this.control && Array.isArray(this.control.value) ? this.control.value.filter(el => el !== null).length : 0;
}
get selectedAll(): boolean {
return this.selectedItemsCount === this.values.length;
}
get selectedPartially(): boolean {
const selectedItemsCount = this.selectedItemsCount;
return selectedItemsCount > 0 && selectedItemsCount < this.values.length;
}
get checkboxState(): MatPseudoCheckboxState {
let state: MatPseudoCheckboxState = 'unchecked';
if (this.selectedAll) {
state = 'checked';
} else if (this.selectedPartially) {
state = 'indeterminate';
}
return state;
}
refresh(): void {
if (this.selectedItemsCount > 0) {
this.select();
} else {
this.deselect();
}
}
}
HTML:
<mat-pseudo-checkbox class="mat-option-pseudo-checkbox"
[state]="checkboxState"
[disabled]="disabled"
[ngClass]="selected ? 'bg-accent': ''">
</mat-pseudo-checkbox>
<span class="mat-option-text">
{{title}}
</span>
<div class="mat-option-ripple" mat-ripple
[matRippleTrigger]="_getHostElement()"
[matRippleDisabled]="disabled || disableRipple">
</div>
CSS:
.bg-accent {
background-color: #2196f3 !important;
}
Upvotes: 0