UI_Dev
UI_Dev

Reputation: 3417

Customizing mat select to allow nested values in angular

I'm customizing angular material select/autocomplete to allow nested dropdowns.

Here, I wanted to have one parent dropdown with many childs. If I expand particular parent dropdown, only childs of that dropdown should expand or collapse. Similarly, checkbox event should be selected in the same scenario.

I have some bugs here

Case 1.

Select A Parent checkbox, C Parent checkbox, expand both, unselect each values of C and then finally unselecting parent C checkbox will give only the first value of A. Expected should be all values of A as parent A was already selected.

There are some additional bugs also there, hope if case 1 is solved, that will get resolved.

Any help would be highly appreciated.

STACKBLITZ

HTML

<mat-form-field appearance="fill">
  <mat-label>Toppings</mat-label>
  <input type="text" matInput placeholder="Select Users" aria-label="Select Users" matInput [matAutocomplete]="auto" [formControl]="states">
  <mat-autocomplete #auto="matAutocomplete">

    <mat-select-trigger>
      {{states.value ? states.value[0] : ''}}
      <span *ngIf="states.value?.length > 1" class="example-additional-selection">
            (+{{states.value.length - 1}} {{states.value?.length === 2 ? 'other' : 'others'}})
          </span>
    </mat-select-trigger>

    <mat-optgroup *ngFor="let group of stateList">
      <div>
        <mat-checkbox [checked]="group.selected" (change)="toggleParent($event, group)" (click)="$event.stopPropagation()">
          {{group.letter}}
        </mat-checkbox>
        <button mat-button (click)="expandDocumentTypes(group)">
                <mat-icon>keyboard_arrow_down</mat-icon>
              </button>
      </div>
      <mat-option *ngFor="let name of group.names" [value]="name" [ngClass]="isExpandCategory[group.letter] ? 'list-show' : 'list-hide'">
        <mat-checkbox [checked]="group.checked" (change)="toggleSelection($event, name, group)" (click)="$event.stopPropagation()">
          {{name}}
        </mat-checkbox>
      </mat-option>
    </mat-optgroup>

  </mat-autocomplete>
</mat-form-field>

TS Code:

export class SelectCustomTriggerExample {
  constructor(private _formBuilder: FormBuilder) {}

  isExpandCategory: boolean[] = [];
  toppingList: string[] = ['Extra cheese', 'Mushroom', 'Onion', 'Pepperoni', 'Sausage', 'Tomato'];
  stateRecord: any = [];
  states = new FormControl();

  expandDocumentTypes(group: any) {
    console.log("expanding dropdown", group);
    this.isExpandCategory[group.letter] = !this.isExpandCategory[group.letter];
  }

  toggleSelection(event: any, name: any, group: any) {
    console.log("toggleSelection", name, event.checked, group);
    if (event.checked) {
      console.log("stastateRecordtelist", this.stateRecord);
      this.stateRecord.push(name);
      this.states.setValue(this.stateRecord);
      console.log("toggleselection ", this.states.value);
    } else {
      console.log("else toggleselection", name, group, this.states.value);
      this.states.setValue(this.states.value.filter((x: any) => x !== name));
      console.log("after filter ", this.states.value);
    }
  }

  toggleParent(event: any, group: any) {
    group.checked = event.checked;
    console.log("event", event.checked, "group", group, "states value", this.states.value);
    let states = this.states.value;
    states = states ? states : [];
    if (event.checked) {
      states.push(...group.names)
    } else {
      console.log("else", states);
      group.names.forEach((x: string) => states.splice(states.indexOf(x), 1));
    }
    this.states.setValue(states);
    console.log("statesvalue", this.states.value);
    if (!event.checked) {
      this.states.setValue(this.states.value.filter((x: any) => !x.includes(group.names)))
    }
    console.log("final statesvalue", this.states.value);
  }

  stateList: StateGroup[] = [{
    letter: 'A',
    checked: false,
    names: ['Alabama', 'Alaska', 'Arizona', 'Arkansas']
  }, {
    letter: 'C',
    checked: false,
    names: ['California', 'Colorado', 'Connecticut']
  }, {
    letter: 'D',
    checked: false,
    names: ['Delaware']
  }, {
    letter: 'F',
    checked: false,
    names: ['Florida']
  }, {
    letter: 'G',
    checked: false,
    names: ['Georgia']
  }, {
    letter: 'H',
    checked: false,
    names: ['Hawaii']
  }, {
    letter: 'I',
    checked: false,
    names: ['Idaho', 'Illinois', 'Indiana', 'Iowa']
  }, {
    letter: 'K',
    checked: false,
    names: ['Kansas', 'Kentucky']
  }, {
    letter: 'L',
    checked: false,
    names: ['Louisiana']
  }, {
    letter: 'M',
    checked: false,
    names: ['Maine', 'Maryland', 'Massachusetts', 'Michigan',
      'Minnesota', 'Mississippi', 'Missouri', 'Montana'
    ]
  }, {
    letter: 'N',
    checked: false,
    names: ['Nebraska', 'Nevada', 'New Hampshire', 'New Jersey',
      'New Mexico', 'New York', 'North Carolina', 'North Dakota'
    ]
  }, {
    letter: 'O',
    checked: false,
    names: ['Ohio', 'Oklahoma', 'Oregon']
  }, {
    letter: 'P',
    checked: false,
    names: ['Pennsylvania']
  }, {
    letter: 'R',
    checked: false,
    names: ['Rhode Island']
  }, {
    letter: 'S',
    checked: false,
    names: ['South Carolina', 'South Dakota']
  }, {
    letter: 'T',
    checked: false,
    names: ['Tennessee', 'Texas']
  }, {
    letter: 'U',
    checked: false,
    names: ['Utah']
  }, {
    letter: 'V',
    checked: false,
    names: ['Vermont', 'Virginia']
  }, {
    letter: 'W',
    checked: false,
    names: ['Washington', 'West Virginia', 'Wisconsin', 'Wyoming']
  }];
}

Output should look like below.

enter image description here

Upvotes: 4

Views: 4411

Answers (2)

Alberto A.M.
Alberto A.M.

Reputation: 73

maybe this comment won't be your exact answer, but can give you an idea on how to solve it.

Basically your problem is that you are combining diferent components such as mat-autocomplete with a mat-select with multiple selections with a mat-optgroup. And this can very risky and can gives you a lot of headaches. In adition it's is not recommended to do that.

In addition you have a formControl, which is: [formControl]="states" inside the input, which is OK, and that's the reasson that you are obtain all the string separated by comma, this is because the value of the formControl.

You can take a look at the documentation of formControls: https://angular.io/guide/reactive-forms

So I highly recommend to change the formControl to the mat-select. (I understand that you will store the value of all the array with subarrays.)

You need to edit the input, to something like that:

<mat-form-field appearance="fill">
  <mat-label>Toppings</mat-label>
    <!-- <input type="text" matInput placeholder="States Group" formControlName="stateGroup" required [matAutocomplete]="autoGroup">
     <mat-autocomplete #autoGroup="matAutocomplete"> -->
           <input type="text" matInput placeholder="" aria-label="" matInput [matAutocomplete]="auto" [formControl]="" style="">
           <span *ngIf="states.value">
           {{states.value ? states.value[0] : ''}}
           (+{{states.value.length - 1}} {{states.value?.length === 2 ? 'other' : 'others'}})
           </span>

Here is the stackblitz: https://stackblitz.com/edit/angular-f5mizr-wefuik

Upvotes: 0

Vivek Jain
Vivek Jain

Reputation: 2864

Find you answer here:: https://stackblitz.com/edit/angular-f5mizr-n7gmx5

This will pass your test case 1. And I found one more case

Select and Unselect any child element multiple time it will repeat duplicate selected value.

This case also fixed in my given stackblitz.

Upvotes: 1

Related Questions