Home > front end >  Angular Reactive form validation and data formation issue
Angular Reactive form validation and data formation issue

Time:10-25

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

CodePudding user response:

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

  • Related