Select All mat option and deselect All
Asked Answered
L

7

34

I have scenario as below:

enter image description here

I want to achieve is:

  1. When user click on All then all options shall be selected and when user click All again then all options shall be deselcted.
  2. If All option is checked and user click any other checkbox than All then All and clicked checkbox shall be deselected.
  3. When user selects 4 options one by one then All shall be selected.

HTML file

<mat-select placeholder="User Type" formControlName="UserType" multiple>
    <mat-option *ngFor="let filters of userTypeFilters" [value]="filters.key">
          {{filters.value}}
    </mat-option>
        <mat-option #allSelected (click)="toggleAllSelection()" [value]="0">All</mat-option>
</mat-select>

TS file

this.searchUserForm = this.fb.group({
  userType: new FormControl('')
});

userTypeFilters = [
  {
    key: 1, value: 'Value 1',
  },
  {
    key: 2, value: 'Value 2',
  },
  {
    key: 3, value: 'Value 3',
  },
  {
    key: 4, value: 'Value 4',
  }
]

toggleAllSelection() {
  if (this.allSelected.selected) {
    this.searchUserForm.controls.userType
    .patchValue([...this.userTypeFilters.map(item => item.key), 0]);
  } else {
    this.searchUserForm.controls.userType.patchValue([]);
  }
}

Now, how to achieve 2nd and 3rd point

Stackblitz is: https://stackblitz.com/edit/angular-material-with-angular-v5-znfehg?file=app/app.component.html

Latish answered 29/7, 2018 at 11:55 Comment(3)
as I see 2nd senario works am I right?Paulin
no.. only 1st works.. 2nd option is that if All is selected and then of user deselcts any option then that option and All option shall be deselctedLatish
https://mcmap.net/q/450743/-how-to-implement-select-all-in-angular-material-drop-down-angular-5Hime
S
52

Use code as below create function on click each mat-option and select()/deselect() all option:

See stackblitz:https://stackblitz.com/edit/angular-material-with-angular-v5-jsgvx6?file=app/app.component.html

TS:

togglePerOne(all){ 
   if (this.allSelected.selected) {  
    this.allSelected.deselect();
    return false;
}
  if(this.searchUserForm.controls.userType.value.length==this.userTypeFilters.length)
    this.allSelected.select();

}
  toggleAllSelection() {
    if (this.allSelected.selected) {
      this.searchUserForm.controls.userType
        .patchValue([...this.userTypeFilters.map(item => item.key), 0]);
    } else {
      this.searchUserForm.controls.userType.patchValue([]);
    }
  }

HTML:

<form [formGroup]="searchUserForm" fxFlex fxLayout="column" autocomplete="off" style="margin: 30px">
    <mat-select placeholder="User Type" formControlName="userType" multiple>
        <mat-option *ngFor="let filters of userTypeFilters" [value]="filters.key" (click)="togglePerOne(allSelected.viewValue)">
            {{filters.value}}
        </mat-option>
        <mat-option #allSelected (click)="toggleAllSelection()" [value]="0">All</mat-option>
    </mat-select>
</form>
Signalman answered 29/7, 2018 at 12:48 Comment(3)
And if I want to avoid seeing "all" in the list of selected items, what do I do?Lytic
sorry, maybe I have not explained myself well, I would like to avoid it being in the list of selected items, but I would not want to remove the respective checkboxLytic
@Lytic Use mat-select-trigger to change the title of selected items.Desmoid
S
40

Simply you can do it without adding a new option to your data source by adding a checkbox.

See the: Demo

import { Component, VERSION, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatSelect } from '@angular/material/select';
import { MatOption } from '@angular/material/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  @ViewChild('select') select: MatSelect;

  allSelected=false;
   foods: any[] = [
    {value: 'steak-0', viewValue: 'Steak'},
    {value: 'pizza-1', viewValue: 'Pizza'},
    {value: 'tacos-2', viewValue: 'Tacos'}
  ];
  toggleAllSelection() {
    if (this.allSelected) {
      this.select.options.forEach((item: MatOption) => item.select());
    } else {
      this.select.options.forEach((item: MatOption) => item.deselect());
    }
  }
   optionClick() {
    let newStatus = true;
    this.select.options.forEach((item: MatOption) => {
      if (!item.selected) {
        newStatus = false;
      }
    });
    this.allSelected = newStatus;
  }
}
.select-all{
  margin: 5px 17px;
} 
<mat-form-field>
  <mat-label>Favorite food</mat-label>
  <mat-select  #select multiple>
    <div class="select-all">
        <mat-checkbox [(ngModel)]="allSelected"
                        [ngModelOptions]="{standalone: true}"
                        (change)="toggleAllSelection()">Select All</mat-checkbox>
    </div>
    <mat-option (click)="optionClick()" *ngFor="let food of foods" [value]="food.value">
      {{food.viewValue}}
    </mat-option>
  </mat-select>
</mat-form-field>
Seka answered 27/6, 2020 at 9:38 Comment(2)
This should be the accepted answer, as it doesn't add an unnecessary extra item to the values.Kaveri
I forked you demo. Instead a checkbox I did it using a button. Encapsulating it in a custom component, check it out: stackblitz.com/edit/angular-material-select-all-buttonSophrosyne
G
20

Another way to do this is with the @ViewChild selector to get the mat-select component and troggle the mat-options items selected or unselected. We need also a variable to save the selected actual status to select or unselect all the elements on every click. Hope will help.

  import {MatOption, MatSelect} from "@angular/material";
  
  export class ExampleAllSelector {
  
    myFormControl = new FormControl();
    elements: any[] = [];

    allSelected = false;

    @ViewChild('mySel') skillSel: MatSelect;

    constructor() {}

    toggleAllSelection() {
      this.allSelected = !this.allSelected;  // to control select-unselect
      
      if (this.allSelected) {
        this.skillSel.options.forEach( (item : MatOption) => item.select());
      } else {
        this.skillSel.options.forEach( (item : MatOption) => {item.deselect()});
      }
      this.skillSel.close();
    }
  }
      <mat-select #mySel placeholder="Example" [formControl]="myFormControl" multiple>
        <mat-option [value]="0" (click)="toggleAllSelection()">All items</mat-option>
        <mat-option *ngFor="let element of elements" [value]="element">{{skill.name}}</mat-option>
      </mat-select>
Goatherd answered 3/5, 2019 at 12:7 Comment(5)
skillAllSelected what should be?Matejka
@Matejka It should be "allSelected"Telfer
Thanks, this worked for me in a case with a loop of selects and instead of using viewChild, i used @ViewChildren('mySel') skillSel: QueryList<MatSelect>; and to get access to the options should be something like const select = this.skillSel.toArray()[index];Matejka
@Goatherd Hi..how can we use the same logic if the select box is dynamic, like on click button multiple select box will be added in DOM, need to do that same functionality in all textbox!Sunshine
this solution emits valueChanges after each item.select() call, which may cause issues in some scenariosSwarth
H
6

Here is an example of how to extend a material option component.

See stackblitz Demo

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;
}
Hestia answered 30/3, 2020 at 13:21 Comment(3)
I love this solution: is there a way to make this keyboard friendly? looks like the custom option is completely ignored by the MatSelect component..Loehr
On Angular 10 I had to remove the call _selectViaInteraction because the Select All option was not changing the state.Furl
Excellent answer. But I was unable to uncheck select all after clicking select all. I had to remove this. _selectViaInteraction(); in click hostlistener because it is getting called from somewhere (It is toggling this.selected). So calling the method again toggles back the selected property . I am using angular 14.Linnet
P
0

Another possible solution:

using <mat-select [(value)]="selectedValues" in the template and set the selectedValues via toggle function in the component.

Working Stackblitz Demo.

Component

export class AppComponent {

  selectedValues: any;
  allSelected = false;

   public displayDashboardValues = [
    {key:'0', valuePositionType: 'undefined', viewValue:'Select all'},
    {key:'1', valuePositionType: 'profit-loss-area', viewValue:'result'},
    {key:'2', valuePositionType: 'cash-area', viewValue:'cash'},
    {key:'3', valuePositionType: 'balance-area', viewValue:'balance'},
    {key:'4', valuePositionType: 'staff-area' ,viewValue:'staff'},
    {key:'5', valuePositionType: 'divisions-area', viewValue:'divisions'},
    {key:'6', valuePositionType: 'commisions-area', viewValue:'commisions'},    
  ];

  toggleAllSelection() {
      this.allSelected = !this.allSelected;
      this.selectedValues = this.allSelected ? this.displayDashboardValues : [];
    }
}

Template

        <mat-select  [(value)]="selectedValues" (selectionChange)="selectionChange($event)" formControlName="dashboardValue" multiple>
          <mat-option [value]="displayDashboardValues[0]" (click)="toggleAllSelection()">{{ displayDashboardValues[0].viewValue }}</mat-option>
          <mat-divider></mat-divider>
          <div *ngFor="let dashboardPosition of displayDashboardValues">
            <mat-option class="dashboard-select-option" *ngIf="dashboardPosition.key>0" [value]="dashboardPosition">
              {{ dashboardPosition.viewValue }}
            </mat-option>
          </div>
        </mat-select>
Phenylamine answered 27/8, 2020 at 7:41 Comment(1)
gets weird when you use arrow keys/click the 'enter' key on select allNiall
S
0

There are some problems with other answers. The most important one is that they're listening to the click event which is not complete (user can select an option via space key on the keyboard).

I've created a component that solves all the problems:

@Component({
  selector: 'app-multi-select',
  templateUrl: './multi-select.component.html',
  styleUrls: ['./multi-select.component.scss'],
})
export class MultiSelectComponent<V> implements OnInit {
  readonly _ALL_SELECTED = '__ALL_SELECTED__' as const;
  @Input() options: ReadonlyArray<{ value: V; name: string }> = [];
  @Input('selectControl') _selectControl!: FormControl | AbstractControl | null | undefined;
  get selectControl(): FormControl {
    return this._selectControl as FormControl;
  }
  @Input() label: string = '';
  @Input() hasSelectAllOption = false;
  selectedValues: (V | '__ALL_SELECTED__')[] = [];

  constructor() {}

  ngOnInit(): void {}

  onSelectAllOptions({ isUserInput, source: { selected } }: MatOptionSelectionChange) {
    if (!isUserInput) return;
    this.setValues(selected ? this.options.map(o => o.value) : []);
  }

  private setValues(values: (V | '__ALL_SELECTED__')[]) {
    const hasAllOptions = ArrayUtils.arraysAreSame(
      values,
      this.options.map(o => o.value),
    );
    if (!values.includes(this._ALL_SELECTED)) {
      if (hasAllOptions) {
        values = [...values, this._ALL_SELECTED];
      }
    } else if (!hasAllOptions) {
      values = values.filter(o => o !== this._ALL_SELECTED);
    }

    setTimeout(() => {
      this.selectedValues = values;
    });
    this.selectControl.setValue(values.filter(o => (o as any) !== this._ALL_SELECTED));
  }

  onSelectOtherOptions({ isUserInput, source: { selected, value } }: MatOptionSelectionChange) {
    if (!isUserInput) return;
    this.setValues(
      selected ? [...this.selectedValues, value] : this.selectedValues.filter(o => o !== value),
    );
  }
}


<mat-form-field>
  <mat-label>Choose some options</mat-label>
  <mat-select multiple [value]="selectedValues">
    <mat-option
      *ngFor="let d of options"
      [value]="d.value"
      (onSelectionChange)="onSelectOtherOptions($event)"
    >
      {{ d.name }}
    </mat-option>
    <mat-option
      *ngIf="hasSelectAllOption"
      [value]="_ALL_SELECTED"
      (onSelectionChange)="onSelectAllOptions($event)"
    >
      Select all
    </mat-option>
  </mat-select>
</mat-form-field>

Slantwise answered 16/12, 2020 at 19:3 Comment(1)
Could you please share complete stackblitz example? ThanksAide
S
0

Based on @Yaseen's answer, here's the one for Reactive form: Demo

import { Component, VERSION, ViewChild } from '@angular/core';
import { FormControl, FormGroup, FormBuilder } from '@angular/forms';
import { MatSelect } from '@angular/material/select';
import { MatOption } from '@angular/material/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  @ViewChild('select') select: MatSelect;
  allSelected = false;
  form: FormGroup;
  foods: any[] = [
    { value: 'steak-0', viewValue: 'Steak' },
    { value: 'pizza-1', viewValue: 'Pizza' },
    { value: 'tacos-2', viewValue: 'Tacos' },
  ];

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      food: [[]],
    });
  }

  toggleAllSelection() {
    const allSelected =
      this.form.get('food').value.length === this.foods.length;
    this.form
      .get('food')
      .setValue(allSelected ? [] : this.foods.map((food) => food.value));
    this.allSelected = !allSelected;
  }

  optionClick() {
    const selectedValues = this.form.get('food').value;
    this.allSelected = selectedValues.length === this.foods.length;
  }
}
.select-all{
  margin: 5px 17px;
}
<mat-form-field [formGroup]="form">
  <mat-label>Favorite food</mat-label>
  <mat-select #select multiple formControlName="food">
    <div class="select-all">
      <mat-checkbox (change)="toggleAllSelection()" [checked]="allSelected"
        >Select All</mat-checkbox
      >
    </div>
    <mat-option
      *ngFor="let food of foods"
      [value]="food.value"
      (click)="optionClick()"
    >
      {{ food.viewValue }}
    </mat-option>
  </mat-select>
</mat-form-field>
Sinusoidal answered 10/5, 2024 at 19:51 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.