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
- For
<mat-option>generated with*ngFor, set[value]="{ skillId: i.skillId }"to return selected value asobject. - Add
compareWithfor your<mat-select>. Purpose for comparingthis.trainerForm.controls.selectedSkillswith[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>
- Set multiple
Validatorsin array[]as mentioned in Issue 2. pickAll()topatchValueforthis.trainerForm.controls.selectedSkillsas{ skillId: item.skillId }Object.onSubmit()before pass the form value to API, make sure you filterskillSetsvalue with{ skillId: item.skillId }Objectonly.compareFnis for comparing the selectedskillSetsvalue 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;
}
