26
votes

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

6
as I see 2nd senario works am I right?לבני מלכה
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 deselctedGags

6 Answers

42
votes

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>
19
votes

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>
19
votes

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>
2
votes

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

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>
0
votes

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>