Template-driven forms rely on directives in the template to create and handle forms. They are best suited for simple scenarios and are easier to set up, making them perfect for beginners.
Setting Up the Project
import { Component } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms';
@Component({
selector: 'app-login',
standalone: true,
templateUrl: './login.component.html',
styleUrl: './login.component.css',
imports: [FormsModule]
})
export class LoginComponent {
onSubmit(formData:NgForm){
// console.log(formData)
if(formData.form.valid) {
return;
}
const enteredEmail = formData.form.value.email;
const enteredPassword = formData.form.value.password;
console.log(enteredEmail, enteredPassword)
}
}
<form #form="ngForm" (ngSubmit)="onSubmit(form)">
<h2>Login</h2>
<div class="control-row">
<div class="control no-margin">
<label for="email">Email</label>
<input id="email" type="email" name="email" ngModel required email/>
</div>
<div class="control no-margin">
<label for="password">Password</label>
<input id="password" type="password" name="password" ngModel required minlength="6" />
</div>
<button class="button">Login</button>
</div>
</form>
Advance usage:
// login.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
loginData = {
email: '',
password: ''
};
onSubmit() {
if (this.loginForm.valid) {
console.log('Form submitted', this.loginData);
// Here you would typically make an API call
}
}
}
<!-- login.component.html -->
<div class="login-container">
<h2>Login</h2>
<form #loginForm="ngForm" (ngSubmit)="onSubmit()">
<!-- Email Field -->
<div class="form-group">
<label for="email">Email</label>
<input
type="email"
id="email"
name="email"
[(ngModel)]="loginData.email"
#email="ngModel"
required
email
class="form-control"
>
<div *ngIf="email.invalid && (email.dirty || email.touched)" class="error-message">
<div *ngIf="email.errors?.['required']">Email is required</div>
<div *ngIf="email.errors?.['email']">Please enter a valid email</div>
</div>
</div>
<!-- Password Field -->
<div class="form-group">
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
[(ngModel)]="loginData.password"
#password="ngModel"
required
minlength="6"
class="form-control"
>
<div *ngIf="password.invalid && (password.dirty || password.touched)" class="error-message">
<div *ngIf="password.errors?.['required']">Password is required</div>
<div *ngIf="password.errors?.['minlength']">Password must be at least 6 characters</div>
</div>
</div>
<button
type="submit"
[disabled]="loginForm.invalid"
class="submit-button"
>
Login
</button>
</form>
</div>
Key Concepts Explained
1. Two-way Data Binding
The [(ngModel)] directive creates two-way data binding between the form inputs and the component’s properties. For example:
[(ngModel)]="loginData.email"
This synchronizes the input value with the loginData.email property.
2. Form Validation
Template-driven forms support both built-in and custom validators:
required: Makes the field mandatoryemail: Validates email formatminlength: Sets minimum length requirement
3. Form State and CSS Classes
Angular automatically adds CSS classes to form controls based on their state:
ng-valid/ng-invalid: Indicates validation stateng-pristine/ng-dirty: Shows if the value has changedng-touched/ng-untouched: Indicates if the field has been interacted with
4. Error Handling
We use template variables to access the NgModel instance and check for errors
#email="ngModel"