0
votes

Trying to add validation at least to select minimum one item from mat-select multipe option. Currently showing error message on page load until user selects one item from select option, which is working fine.

expectation:
When i select all or single select, error message should disappear. (expected and working fine).

But what happening:
Required error message is not showing when i deselect the selected single item.

Don't know what I'm doing wrong.

skillForm.component.ts

skillList = [
    { skillId: '0', skillName: 'JS' },
    { skillId: '1', skillName: 'TS' },
    { skillId: '2', skillName: 'JAVA' },
    { skillId: '3', skillName: '.Net' },
];

@ViewChild('pickAllCourse') private pickAllCourse: MatOption;
trainerForm = FormGroup;

constructor(public formBuilder: FormBuilder) { }

this.trainerForm = this.formBuilder.group({
    selectedSkills :['', Validators.required, Validators.minLength(1)]
})

pickAll(): void {
    if(this.pickAllCourse.selected) {
    this.trainerForm.controls.selectedSkills.patchValue([...this.skillList.map((item) => item.deviceId), 0]);
    } else {
        this.trainerForm.controls.selectedSkills.patchValue([]);
    }
}


selectOneItem(all): any {
    if (this.pickAllCourse.selected) {
        this.pickAllCourse.deselect();
        return false;
    }
    if (this.trainerForm.controls.selectedSkills.value.length === this.skillList.length) {
        this.pickAllCourse.select();
    }
}

onSubmit(): void{
    console.log('form value', this.trainerForm.value)
    
    // 
}

skillForm.component.html

    <mat-form-field class="selectedSkills">
        <mat-select multiple ngDefaultControl formControlName="selectedSkills"
            placeholder="Select Device Type">
            <mat-option #pickAllCourse (click)="pickAll()" [value]="0">All</mat-option>
            
        <mat-option *ngFor="let i of skillList" [value]="i.deviceId"
            (click)="selectOneItem(pickAllCourse.viewValue)">{{ i.skillName }}
        </mat-option>
        
        </mat-select>
<span class="text-danger" *ngIf="trainerForm.controls['selectedSkills '].invalid ">This field is required</span>
    </mat-form-field>

Additionally, i need help on how to construct the object like below when submit the form for backend.

skillList: [
    {skillId: '0'},
    {skillId: '1'}
];

when i do console.log the this.trainerForm.value, I'm seeing skillList: ['0']

1
Could you please share your error message? - Batajus
Yes. Already updated my SO, @Batajus you can span underneath of mat-select - Jade
Not sure if this is the issue but something I saw. When you initialized your form in the TS file, this selectedSkills =['', Validators.required, Validators.minLength(1)] should probably be selectedSkills: ['', Validators.required, Validators.minLength(1)] with a : instead of =. - Jason White
@JasonWhite that's not issue. actually it my typo. see updated so. Thanks - Jade

1 Answers

1
votes

Issue(s) & Concern(s)

Issue 1:

There is typo error for extra spacing on trainerForm.controls['selectedSkills '].

<span class="text-danger" *ngIf="trainerForm.controls['selectedSkills '].invalid ">This field is required</span>

Issue 2:

If the form control(s) requires multiple Validators, you should group them with an Array.

this.trainerForm = this.formBuilder.group({
  selectedSkills :['', Validators.required, Validators.minLength(1)]
})

Change to:

this.trainerForm = this.formBuilder.group({
  selectedSkills :['', [Validators.required, Validators.minLength(1)]]
})

Concern 1

From HTML and Typescript part, the selectedSkills will return an array of number, but not an array of Object. As you use item.deviceId (return string) and deviceId is not exist in Object for skillLists. I assume that you are using item.skillId.

pickAll(): void {
  if(this.pickAllCourse.selected) {
    this.trainerForm.controls.selectedSkills.patchValue([...this.skillList.map((item) => item.deviceId), 0]);
  } else {
    this.trainerForm.controls.selectedSkills.patchValue([]);
  }
}
<mat-option *ngFor="let i of skillList" [value]="i.deviceId"
    (click)="selectOneItem(pickAllCourse.viewValue)">{{ i.skillName }}
</mat-option>

Hence, when you console.log(this.trainerForm.value), it will display:

{ selectedSkills: [1, 2, 3] }

Solution

  1. For <mat-option> generated with *ngFor, set [value]="{ skillId: i.skillId }" to return selected value as object.
  2. Add compareWith for your <mat-select>. Purpose for comparing this.trainerForm.controls.selectedSkills with [value]="{ skillId: i.skillId }" to check/uncheck the options when select/deselect All.
<mat-form-field class="selectedSkills">
    <mat-select
      multiple
      ngDefaultControl
      formControlName="selectedSkills"
      placeholder="Select Device Type"
      [compareWith]="compareFn"
    >
      <mat-option #pickAllCourse (click)="pickAll()" [value]="0"
        >All</mat-option
      >

      <mat-option
        *ngFor="let i of skillList"
        [value]="{ skillId: i.skillId }"
        (click)="selectOneItem(pickAllCourse.viewValue)"
        >{{ i.skillName }}
      </mat-option>
    </mat-select>
    <span
      class="text-danger"
      *ngIf="trainerForm.controls['selectedSkills'].invalid"
      >This field is required</span
    >
</mat-form-field>
  1. Set multiple Validators in array [] as mentioned in Issue 2.
  2. pickAll() to patchValue for this.trainerForm.controls.selectedSkills as { skillId: item.skillId } Object.
  3. onSubmit() before pass the form value to API, make sure you filter skillSets value with { skillId: item.skillId } Object only.
  4. compareFn is for comparing the selected skillSets value with each <mat-option> value. Hence, when Select All, all the <mat-option> will be selected and vice versa as (2).
trainerForm: FormGroup;

ngOnInit() {
  this.trainerForm = this.formBuilder.group({
    selectedSkills: ['', [Validators.required, Validators.minLength(1)]],
  });
}

pickAll(): void {
  if (this.pickAllCourse.selected) {
    this.trainerForm.controls.selectedSkills.patchValue([
      ...this.skillList.map((item) => {
        return {
          skillId: item.skillId
        };
      }),
      0,
    ]);
  } else {
    this.trainerForm.controls.selectedSkills.patchValue([]);
  }
}

onSubmit(): void {
  console.log('form value', this.trainerForm.value);

  let postFormValue: any = {
    ...this.trainerForm.value,
    selectedSkills: this.trainerForm.value.selectedSkills.filter(
      (x: any) => typeof x === 'object'
    ),
  };

  console.log(postFormValue);
}

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

Sample Solution on StackBlitz