Gags
Gags

Reputation: 3829

Select All mat option and deselect All

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

Upvotes: 34

Views: 142246

Answers (7)

CLAN
CLAN

Reputation: 11

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>

Upvotes: 0

Artem Horovyi
Artem Horovyi

Reputation: 61

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;
}

Upvotes: 6

לבני מלכה
לבני מלכה

Reputation: 16251

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>

Upvotes: 52

Vahid
Vahid

Reputation: 7551

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>

Upvotes: 0

zerocewl
zerocewl

Reputation: 12774

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>

Upvotes: 0

Yaseen
Yaseen

Reputation: 628

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>

Upvotes: 40

Marc
Marc

Reputation: 409

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>

Upvotes: 20

Related Questions