1

I have a mat-select form element tied to a FormControl for validation. The goal is to give an error box below the element if the select box isn't filled in (or nothing is selected)

However the form is valid when submitting with no select option is chosen, even though I have a required option for it.

<form [formGroup]="cityForm" (submit)="submit()">

<mat-form-field>
   <mat-label>Select City</mat-label>

   <mat-select [(ngModel)]="city.Id"
               [formControl]="cityFormControl"
               [errorStateMatcher]="matcher">
                <mat-option *ngFor="let c of dropdowns.Cities"
                            [value]="c.Id">
                    {{c.Name}}
                </mat-option>
   </mat-select>

   <mat-error *ngIf="cityFormControl.hasError('required')">
     City is <strong>required</strong>
   </mat-error>

Controller code:

//Angular Imports
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';

@Component({
    ...
})
export class CityComponent implements OnInit {

    city: any = {};
    dropdowns: any = {};

    //      Form Control Variables
    matcher = new MyErrorStateMatcher();

    cityFormControl = new FormControl('', [Validators.required]);

    cityForm = new FormGroup({
        city: this.cityFormControl
    });


    constructor() {
    }

    ngOnInit(): void {

       this.dropdowns = [
                         {Id: 1, Name: "Toronto"}, 
                         {Id: 2, Name: "Chicago"}
                        ];
    }

    submit(form : FormGroup): void {

        if (form.valid == false) {
            //doesn't get called
            return;
        }

        return;  
}

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
    isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
        const isSubmitted = form && form.submitted;
        return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
    }
}

Any idea why the required validation is not working?

Jnr
  • 1,504
  • 2
  • 21
  • 36
Jebathon
  • 4,310
  • 14
  • 57
  • 108
  • probably bc you're mixing ngModel and form controls, choose one – bryan60 Nov 13 '19 at 17:04
  • I have never seen form groups set up this way is there a reason you are not creating it at once? `cityForm = new FormGroup({ city:new FormControl('', [Validators.required]) });` – DanGo Nov 13 '19 at 17:09
  • @bryan60 How can you tie in a value without ngModel? – Jebathon Nov 13 '19 at 18:01
  • use setValue on your form control. I can show you how if you show me how / when city is defined. – bryan60 Nov 13 '19 at 18:02

2 Answers2

2

You need to provide the control name and then let the material form handle the error by calling valid on the control you defined.

<form [formGroup]="cityForm" (submit)="submit()">

<mat-form-field>
   <mat-label>Select City</mat-label>

   <mat-select formControlName="cityFormControl"  // Form group is set by form tag
               [compareWith]="CompareCity">       // You need to define a compare method
                <mat-option *ngFor="let c of dropdowns.Cities"
                            [value]="c.Id">
                    {{c.Name}}
                </mat-option>
   </mat-select>

   <mat-error *ngIf="!cityForm.controls['cityFormControl'].valid && cityForm.controls['cityFormControl'].touched">
     City is <strong>required</strong>
   </mat-error>

A compare function would look something like this:

  public CompareCity(Param1: City, Param2: City) : boolean {
    return Param1 && Param2 ? Param1.Id === Param2.Id : false;
  }

J. Kramer
  • 63
  • 7
1

Refer to What are the practical differences between template-driven and reactive forms?

Please have a look at the following example:

constructor(private fb: FormBuilder) { }

cityForm: FormGroup;

ngOnInit(): void {
  this.cityForm = this.fb.group({
    city: ['', [Validators.required]]
  })
}
get city() { return this.cityForm.get("city") }
<form [formGroup]="cityForm" (submit)="submit()">
  <mat-select [formControl]="city" [errorStateMatcher]="matcher">
    <mat-option *ngFor="let c of dropdowns.Cities" [value]="c.Id">
      {{c.Name}}
    </mat-option>
  </mat-select>

  <mat-error *ngIf="city.hasError('required')">
    City is <strong>required</strong>
  </mat-error>
</form>

Rafi Henig
  • 5,950
  • 2
  • 16
  • 36