Elliott Alderson
Elliott Alderson

Reputation: 35

Submitting valid form and changing style

I have an Angular 15 App. I am using an Angular material for styling. I want to submit only valid form, i.e. none of the fields should be empty and e-mail field must be an email.

This is how my form looks like:




`       <form #form="ngForm" (ngSubmit)="onFormSubmit()" class="py-3">
            <div class="row">

              <mat-form-field class="col">
                <mat-label>First name</mat-label>
                <input matInput placeholder="Ex: Jhon" [formControl]="fnameFormControl" [errorStateMatcher]="matcher" [(ngModel)]="model.fname" name="fname">
                <mat-error *ngIf="fnameFormControl.hasError('required')">
                  Field is <strong>required</strong>
                </mat-error>
              </mat-form-field>
              <mat-form-field class="col">
                <mat-label>Last name</mat-label>
                <input matInput placeholder="Ex: Doe" [formControl]="lnameFormControl" [errorStateMatcher]="matcher" [(ngModel)]="model.lname" name="lname">
                <mat-error *ngIf="lnameFormControl.hasError('required')">
                  Field is <strong>required</strong>
                </mat-error>
              </mat-form-field>
            </div>
            <div class="row pb-3">
              <div class="col">
                <input type="tel" id="phone" class="w-100 form-control" [(ngModel)]="model.phone" name="phone">
                <span id="error-msg" class="d-none error-msg"></span>
              </div>
            </div>

            <mat-form-field class="form-group w-100">
              <mat-label>Email</mat-label>
              <input type="email" matInput [formControl]="emailFormControl" [errorStateMatcher]="matcher" [(ngModel)]="model.email" name="email"
                     placeholder="Ex. [email protected]">
              <!-- <mat-hint>Errors appear instantly!</mat-hint> -->
              <mat-error *ngIf="emailFormControl.hasError('email') && !emailFormControl.hasError('required')">
                Please enter a valid email address
              </mat-error>
              <mat-error *ngIf="emailFormControl.hasError('required')">
                Email is <strong>required</strong>
              </mat-error>
            </mat-form-field>

              <mat-form-field class="w-100">
                <mat-label>Choose a breed</mat-label>
                <mat-select [formControl]="breedSelectFormControl" [errorStateMatcher]="matcher" [(ngModel)]="model.breed" name="breed">
                  <mat-option *ngFor="let breedof breeds" [value]="breed.value">
                    {{breed.viewValue}}
                  </mat-option>
                </mat-select>
                <mat-error *ngIf="breedSelectFormControl.hasError('required')">
                  Please select on option
              </mat-error>
              </mat-form-field>

              <mat-form-field class="w-100">
                <mat-label>Share your story with us</mat-label>
                <textarea matInput placeholder="Write it here..." [formControl]="storyFormControl" [errorStateMatcher]="matcher" [(ngModel)]="model.story" name="story"></textarea>
                <mat-error *ngIf="storyFormControl.hasError('required')">
                  Field is <strong>required</strong>
                </mat-error>
              </mat-form-field>

              <div class="form-group pt-3">
                  <button class="btn btn-warning btn-block w-100 " id="contact-btn">Get a free consultation</button>
              </div>
          </form>`



Same component's .ts logic looks like this:




import { Component } from '@angular/core';
import {
  FormControl,
  FormGroupDirective,
  NgForm,
  Validators,
  FormsModule,
  ReactiveFormsModule,
} from '@angular/forms';
import {MatSelectModule} from '@angular/material/select';
import {ErrorStateMatcher} from '@angular/material/core';
import {NgIf, NgFor} from '@angular/common';
import {MatInputModule} from '@angular/material/input';
import {MatFormFieldModule} from '@angular/material/form-field';
import { addBreed Request } from '../model/add-breed-request.model';

/** 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));
  }
}

interface Breed {
  value: string;
  viewValue: string;
}

@Component({
  selector: 'app-contact-card',
  templateUrl: './contact-card.component.html',
  styleUrls: ['./contact-card.component.css'],
  standalone: true,
  imports: [FormsModule, MatSelectModule , MatFormFieldModule, MatInputModule, ReactiveFormsModule, NgIf , NgFor],
})
export class ContactCardComponent {
  //base model
  model: addBreedRequest;

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

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

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

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

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

  matcher = new MyErrorStateMatcher();

  breeds: Breed[] = [
    {value: '1', viewValue: 'German Shepherd'},
    {value: '2', viewValue: 'German Longhaired Pointer'},
    {value: '3', viewValue: 'German Pinscher'},
    {value: '4', viewValue: 'German Shepherd Dog'},
    {value: '5', viewValue: 'German Shorthaired Pointer'},
    {value: '6', viewValue: 'German Wirehaired Pointer'},
    {value: '7', viewValue: 'German Spitz'}
  ];

  constructor(){
      this.model = {
        fname: '',
        lname: '',
        email: '',
        phone: '',
        breed: '',
        story: ''
      }
  }

  onFormSubmit(){
    // here I want to check if form is valid.
    console.log(this.model);
    // expectations:
    if(form.isValid()){
    // submit request and change form style.
   }
  }
}




Visually validation works perfectly but when I tried to pass the form to onFormSubmit(), even with empty fields it was showing that form was valid.

I tried something like this:

<form #form="ngForm" (ngSubmit)="onFormSubmit(**form**)" class="py-3">

// and in .ts file
  onFormSubmit(form:FormControl){
    console.log(form.isValid()); // outputing valid all the time.
  }

Updated: Also I am getting this message in browser console:

It looks like you're using ngModel on the same form field as formControl. Support for using the ngModel input property and ngModelChange event with reactive form directives has been deprecated in Angular v6 and will be removed in a future version of Angular.

For more information on this, see our API docs here: https://angular.io/api/forms/FormControlDirective#use-with-ngmodel

Upvotes: 0

Views: 213

Answers (1)

Yong Shun
Yong Shun

Reputation: 51325

You shouldn't mix the Angular Form (NgForm) with the Reactive Form.

As you are setting the validation rule (for example Validators.required) to the FormControl, the validation will only take place in FormControl. While the NgForm instance is not applied with validation(s), therefore with form.valid returns true and conflicts with the result from FormControls.

Provided answer to migrate the current code to Reactive Form.

  1. Remove [(ngModel)] from the HTML.

  2. Create a FormGroup instance that contains multiple FormControls.

  3. Add the [formGroup]="form" attribute in the <form> element.

  4. Replace [formControl] with formControlName.

  5. Revamp all the FormControl variable to a getter function that retrieves the respective FormControl from the form FormGroup instance.

  6. You can patch the model value to FormGroup via patchValue().

Your final code should be as below:

<form [formGroup]="form" (ngSubmit)="onFormSubmit()" class="py-3">
  <div class="row">
    <mat-form-field class="col">
      <mat-label>First name</mat-label>
      <input
        matInput
        placeholder="Ex: Jhon"
        formControlName="fname"
        [errorStateMatcher]="matcher"
        name="fname"
      />
      <mat-error *ngIf="fnameFormControl.hasError('required')">
        Field is <strong>required</strong>
      </mat-error>
    </mat-form-field>
    <mat-form-field class="col">
      <mat-label>Last name</mat-label>
      <input
        matInput
        placeholder="Ex: Doe"
        formControlName="lname"
        [errorStateMatcher]="matcher"
        name="lname"
      />
      <mat-error *ngIf="lnameFormControl.hasError('required')">
        Field is <strong>required</strong>
      </mat-error>
    </mat-form-field>
  </div>
  
  <div class="row pb-3">
    <mat-form-field class="col">
      <mat-label>Phone</mat-label>
      <input
        matInput
        type="tel"
        class="w-100 form-control"
        formControlName="phone"
        [errorStateMatcher]="matcher"
        name="phone"
      />
      <mat-error *ngIf="phoneFormControl.hasError('required')">
        Field is <strong>required</strong>
      </mat-error>
    </mat-form-field>
  </div>

  <mat-form-field class="form-group w-100">
    <mat-label>Email</mat-label>
    <input
      type="email"
      matInput
      formControlName="email"
      [errorStateMatcher]="matcher"
      name="email"
      placeholder="Ex. [email protected]"
    />
    <!-- <mat-hint>Errors appear instantly!</mat-hint> -->
    <mat-error
      *ngIf="emailFormControl.hasError('email') && !emailFormControl.hasError('required')"
    >
      Please enter a valid email address
    </mat-error>
    <mat-error *ngIf="emailFormControl.hasError('required')">
      Email is <strong>required</strong>
    </mat-error>
  </mat-form-field>

  <mat-form-field class="w-100">
    <mat-label>Choose a breed</mat-label>
    <mat-select
      formControlName="breed"
      [errorStateMatcher]="matcher"
      name="breed"
    >
      <mat-option *ngFor="let breed of breeds" [value]="breed.value">
        {{breed.viewValue}}
      </mat-option>
    </mat-select>
    <mat-error *ngIf="breedSelectFormControl.hasError('required')">
      Please select on option
    </mat-error>
  </mat-form-field>

  <mat-form-field class="w-100">
    <mat-label>Share your story with us</mat-label>
    <textarea
      matInput
      placeholder="Write it here..."
      formControlName="story"
      [errorStateMatcher]="matcher"
      name="story"
    ></textarea>
    <mat-error *ngIf="storyFormControl.hasError('required')">
      Field is <strong>required</strong>
    </mat-error>
  </mat-form-field>

  <div class="form-group pt-3">
    <button class="btn btn-warning btn-block w-100" id="contact-btn">
      Get a free consultation
    </button>
  </div>
</form>
form = new FormGroup({
  email: new FormControl('', [Validators.required, Validators.email]),
  fname: new FormControl('', [Validators.required]),
  lname: new FormControl('', [Validators.required]),
  phone: new FormControl('', [Validators.required]),
  breed: new FormControl('', [Validators.required]),
  story: new FormControl('', [Validators.required]),
});

get emailFormControl() {
  return this.form.controls['email'] as FormControl;
}

get fnameFormControl() {
  return this.form.controls['fname'] as FormControl;
}

get lnameFormControl() {
  return this.form.controls['lname'] as FormControl;
}

get phoneFormControl() {
  return this.form.controls['phone'] as FormControl;
}

get breedSelectFormControl() {
  return this.form.controls['breed'] as FormControl;
}

get storyFormControl() {
  return this.form.controls['story'] as FormControl;
}

ngOnInit() {
  this.form.patchValue(this.model);
}

onFormSubmit() {
  console.log(this.form.valid);
}

Demo @ StackBlitz

Upvotes: 0

Related Questions