Reputation: 35
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
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 FormControl
s.
Provided answer to migrate the current code to Reactive Form.
Remove [(ngModel)]
from the HTML.
Create a FormGroup
instance that contains multiple FormControl
s.
Add the [formGroup]="form"
attribute in the <form>
element.
Replace [formControl]
with formControlName
.
Revamp all the FormControl
variable to a getter function that retrieves the respective FormControl
from the form
FormGroup
instance.
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);
}
Upvotes: 0