kunal
kunal

Reputation: 165

How to implement select all in angular material drop down angular 5

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

Answers (4)

shhdharmen
shhdharmen

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

Chellappan வ
Chellappan வ

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

Moaaz K&#39;
Moaaz K&#39;

Reputation: 56

I extended Angular Material's MatSelect to include following features:

  • Search
  • Grouping
  • Select All
  • Select all options under a group

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

HTML

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

TS

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

Artem Horovyi
Artem Horovyi

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

Related Questions