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
compareWith
for your<mat-select>
. Purpose for comparingthis.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>
- Set multiple
Validators
in array[]
as mentioned in Issue 2. pickAll()
topatchValue
forthis.trainerForm.controls.selectedSkills
as{ skillId: item.skillId }
Object
.onSubmit()
before pass the form value to API, make sure you filterskillSets
value with{ skillId: item.skillId }
Object
only.compareFn
is for comparing the selectedskillSets
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;
}