Jade
Jade

Reputation: 69

Unable to fix and implement Cascading dropdown - with All option (both in parent and child dropdown)

Trying to implement role based restriction while creating users, For that am creating cascading select option using Angular Material select Multiple with 'All' option.


stackblitz

But can't proceed further, stuck in first step itself. Here the problem is i couldn't find and load childModule data which has relation with parent using parentModuleId

this.loadChildModule = this.parentModuleList.find((parent: any) => parent.parentModuleName === event.target.value).parentModuleName;

Scenario 1 if select 'All' option from parent dropdown, child dropdown should be loaded with corresponding data

example:

dropdown 1 // selecting All
parentModuleName: 'All'

dropdown 2 // load child of parent
    childModuleName: 'PM0 CH-1'
    childModuleName: 'PM0 CH-2'
    childModuleName: 'PM0 CH-3'

    childModuleName: 'PM1 CH-1'
    childModuleName: 'PM2 CH-2'
    childModuleName: 'PM3 CH-3'

    childModuleName: 'PM2 CH-1'
    childModuleName: 'PM2 CH-2'
    childModuleName: 'PM3 CH-3'

Scenario 2 if select single option from parent dropdown, child dropdown should be loaded with corresponding parent's child data

     example:

        dropdown 1 // selecting single item
        parentModuleName: 'Parent Module 0'
    
        dropdown 2 // load child of parent
            childModuleName: 'PM0 CH-1'
            childModuleName: 'PM0 CH-2'
            childModuleName: 'PM0 CH-3'

HTML

 <mat-form-field class="permission-module">
    <mat-select multiple ngDefaultControl formControlName="parentModule" placeholder="Select Package Files"
        [compareWith]="compareFn">
        <mat-option #selectAllParentModule (click)="selectAll()" [value]="0">
            All
        </mat-option>
        <mat-option *ngFor="let module of parentModuleList"
            [value]="{id:module.parenModuleId, name: module.parentModuleName}"
            (click)="selectSingleItem(selectAllModule.viewValue, $event)">
            {{module.parentModuleName}}
        </mat-option>
    </mat-select>
</mat-form-field>


<mat-form-field class="permission-module">
    <mat-select multiple ngDefaultControl formControlName="childModule" placeholder="Select Package Files"
        [compareWith]="compareFn">
        <mat-option #selectAllChildModule (click)="selectAll()" [value]="0">
            All
        </mat-option>
        <mat-option *ngFor="let module of childModuleList"
            [value]="{id:module.childModuleId, name: module.childModuleName}"
            (click)="selectSingleItem(selectAllModule.viewValue)">
            {{module.childModuleName}}
        </mat-option>
    </mat-select>
</mat-form-field>

TS

@ViewChild('selectAllParentModule') private selectAllParentModule: MatOption;
@ViewChild('selectAllChildModule') private selectAllChildModule: MatOption;

parentModuleList = [
    { parenModuleId: 0, parentModuleName: 'Parent Module 0' },
    { parenModuleId: 1, parentModuleName: 'Parent Module 1' },
    { parenModuleId: 2, parentModuleName: 'Parent Module 2' },
  ];

  childModuleList = [
    { childModuleId: 0, childModuleName: 'PM0 CH-1', parenModuleId: 0 },
    { childModuleId: 1, childModuleName: 'PM0 CH-2', parenModuleId: 0 },
    { childModuleId: 2, childModuleName: 'PM0 CH-3', parenModuleId: 0 },
    { childModuleId: 3, childModuleName: 'PM0 CH-4', parenModuleId: 0 },

    { childModuleId: 4, childModuleName: 'PM1 CH-1', parenModuleId: 1 },
    { childModuleId: 5, childModuleName: 'PM1 CH-2', parenModuleId: 1 },
    { childModuleId: 6, childModuleName: 'PM1 CH-3', parenModuleId: 1 },

    { childModuleId: 7, childModuleName: 'PM2 CH-1', parenModuleId: 2 },
    { childModuleId: 8, childModuleName: 'PM2 CH-2', parenModuleId: 2 },
    { childModuleId: 9, childModuleName: 'PM2 CH-3', parenModuleId: 3 },
    { childModuleId: 10, childModuleName: 'PM2 CH-4', parenModuleId: 3 },
  ];
  testForm: FormGroup;

  this.testForm = this.formBuilder.group({
    parentModule: [''],
    childModule: ['']
  });


  selectAll(): void {

    /*  select all for parent module */
    console.log('select all', this.rolesForm.controls.parentModule.value);
    if (this.selectAllParentModule.selected) {
      this.rolesForm.controls.parentModule.patchValue([
        ...this.parentModuleList.map((item) => {
          return {
            id: item.parenModuleId,
            name: item.parentModuleName
          };
        }), 0,
      ]);
    } else {
      this.rolesForm.controls.parentModule.patchValue([]);
    }

    /*  select all for child module */
    if (this.selectAllChildModule.selected) {
      this.rolesForm.controls.childModule.patchValue([
        ...this.childModuleList.map((item) => {
          return {
            id: item.childModuleId,
            name: item.childModuleName
          };
        }), 0,
      ]);
    } else {
      this.rolesForm.controls.childModule.patchValue([]);
    }

  }

  selectSingleItem(event): any {
    console.log('single select', this.rolesForm.controls.parentModule.value);

    // this.loadChildModule = this.parentModuleList.find((parent: any) => parent.parentModuleName === event.target.value).parentModuleName;

    /*  single select for parent module */
    if (this.selectAllParentModule.selected) {
      this.selectAllParentModule.deselect();
      return false;
    }
    if (this.rolesForm.controls.parentModule.value !== null &&
      (this.rolesForm.controls.parentModule.value.length === this.parentModuleList.length)) {
      this.selectAllParentModule.select();
    }

    /*  single select for child module */
    if (this.selectAllChildModule.selected) {
      this.selectAllChildModule.deselect();
      return false;
    }
    if (this.rolesForm.controls.childModle.value !== null &&
      (this.rolesForm.controls.childModle.value.length === this.childModuleList.length)) {
      this.selectAllChildModule.select();
    }


  }

  compareFn(obj1: any, obj2: any): boolean {
    return obj1 && obj2 ? obj1.moduleId === obj2.moduleId : obj1 === obj2;
  }

I don't understand what am doing wrong.

Could someone pls help me on this? Hitting my head for past 1 1/2 days

Update

my another simplified version, which also not working. Getting id of undefined error when iterating inside checkUncheckAll() method

my another try

Upvotes: 1

Views: 291

Answers (1)

Eliseo
Eliseo

Reputation: 57939

The fisrt step is simple the code adding a "checkAll" like this SO. It's only add a div between the select and the option

  <mat-select ...>
      <div class="mat-option">
        <mat-checkbox ...>Select All</mat-checkbox>
      </div>
        <!--here the mat-options-->
      <mat-option...>
      <mat-option...>
    </div>

We are going to use two auxiliars variables to control this "allChecked"

  selectAllParent:boolean=false
  allSelected:boolean=false

Futhermore, I choose only store the "id" in the FormControls (you're store the object, but this is unnecesary (*), you can simply create two functions)

getparent(){
  const value=this.testForm.value.parentModule
  return this.parentModuleList.filter(x=>value.includes(x.parenModuleId))
}
getchild(){
  const value=this.testForm.value.childModule
  return this.childModuleList.filter(x=>value.includes(x.childModuleId)).map(x=>(
      {childModuleId:x.childModuleId,
        childModuleName:x.childModuleName
      }
    ))
}

And use if you need, e.g. when you want to send to a service or you want to store -tipical in submit function-

Remember that a multiple matselect store the values in an array

As we are using [(ngModel)] -the variables don't belong to the formGroup- we need use [ngModelOptions]={standalone:true}. For each one we need two functions, one to "toggle" the selection and another one that, when change the select, change the variables to show checked or not.

The second mat-select has all the options that has parenModuleId is included in the value of testForm.get('parentModule'): *ngIf="testForm.get('parentModule').value && testForm.get('parentModule').value.includes(child.parenModuleId))"

So our .html becomes like:

<form [formGroup]="testForm" (submit)="submit()">
<mat-form-field class="permission-module">
  <mat-select (selectionChange)="selectionParentChange()"
    multiple
    ngDefaultControl
    formControlName="parentModule"
    placeholder="Select parent module"
  >
    <div class="mat-option">
      <mat-checkbox
        [(ngModel)]="selectAllParent"
        [ngModelOptions]="{standalone: true}"
        (change)="toggleAllSelection($event.checked)"
        >Select All</mat-checkbox
      >
    </div>
    <mat-option
      *ngFor="let module of parentModuleList"
      [value]="module.parenModuleId"
    >
      {{module.parentModuleName}}
    </mat-option>
  </mat-select>
</mat-form-field>

<br />
<mat-form-field class="permission-module">
  <mat-select (selectionChange)="selectionChildChange()"
    multiple
    ngDefaultControl
    formControlName="childModule"
    placeholder="Select child modules"
  >
    <div class="mat-option">
      <mat-checkbox [(ngModel)]="allSelected"
        [ngModelOptions]="{standalone: true}"
        (change)="toggleAllChild($event.checked)"
        >Select All</mat-checkbox
      >
    </div>
    <ng-container *ngFor="let child of childModuleList">
        <mat-option *ngIf="testForm.get('parentModule').value && 
            testForm.get('parentModule').value.includes(child.parenModuleId)"
          [value]="child.childModuleId"
        >
          {{child.childModuleName}}
        </mat-option>
    </ng-container>
  </mat-select>
</mat-form-field>
<br/>
<button mat-raised-button color="primary">Submit</button>
</form>

The auxiliars functions:

  toggleAllSelection(checked: boolean) {
    this.testForm
      .get('parentModule')
      .setValue(
        checked ? this.parentModuleList.map((x) => x.parenModuleId) : null
      );
  }

  toggleAllChild(checked: boolean) {
    const parent = this.testForm.get('parentModule').value;
    const value = checked
      ? this.childModuleList
          .filter((x) => parent.includes(x.parenModuleId))
          .map((x) => x.childModuleId)
      : null;
    this.testForm.get('childModule').setValue(value);
  }

  selectionParentChange() {
    this.selectAllParent =
      this.testForm.get('parentModule').value.length ==
      this.parentModuleList.length;
  }

  selectionChildChange() {
    const parent = this.testForm.get('parentModule').value;
    const value = this.childModuleList.filter(
      (x) => parent.includes(x.parenModuleId)
    );
    this.allSelected =
      value.length == this.testForm.get('childModule').value.length;
  }

The stackblitz

Update we need check if, when we has selected a childrenModule and deselect a ChildrenParent, to remove this values. So our function selectionParentChange() becomes like

  selectionParentChange() {
    this.selectAllParent = ...

      //Here: we need check if there some selected in childModule that it's not
      //in parent
      const parent = this.testForm.get('parentModule').value;
      const child=this.testForm.get('childModule').value;
      const value=child.filter(x=>{
           const child=this.childModuleList.find(c=>c.childModuleId==x)
           return child && parent.includes(child.parenModuleId)
      })
      if (child!=value)
        this.testForm.get('childModule').setValue(value)
  }

(*)you can do it changing a bit the functions related

Upvotes: 2

Related Questions