Don’t forget to import FormsModule in app.module.ts
Required

Template driven approach
Button
Enable button only if the form is valid
<button type="submit" [disabled]="!f.valid">Submit</button>
ts
@ViewChild('f') signupForm: NgForm;
Text field
This will be treated as invalid if it’s empty
<input type="text" id="username" ngModel name="username" required>
<input type="email" id="email" ngModel name="email" required email #email="ngModel"> <span class="help-block" *ngIf="!email.valid && email.touched">Please enter a valid email!</span>
Classes added by Angular automatically
class="ng-valid"
class="ng-invalid"
input.ng-invalid.ng-touched{
border: 1px solid red;
}

Select
Set default value of a dropdown
html
<select
id="secret"
class="form-control"
[ngModel]="defaultQuestion"
name="secret">
<option value="pet">Your first Pet?</option>
<option value="teacher">Your first teacher?</option>
</select>
ts
defaultQuestion = 'teacher';

Radio
fruits = ['apple', 'peach', 'grape'];
<div class="radio" *ngFor="let fruit of fruits">
<label>
<input
type="radio"
name="fruit"
ngModel
[value]="fruit"
required> {{fruit}}
</label>
</div>
Template Driven Form Validation
HTML
<button (click)="showUserForm = !showUserForm" class="btn btn-dark mb-3">Add User</button>
<div class="card card-body mb-3" *ngIf="showUserForm">
<h2>Add User</h2>
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<div class="form-group">
<label>First Name</label>
<input
type="text"
[(ngModel)]="user.firstName"
[ngClass]="{'is-invalid':userFirstName.errors && userFirstName.touched}"
class="form-control"
name="firstName"
#userFirstName="ngModel"
required
minlength="2"
>
<div [hidden]="!userFirstName.errors?.required" class="invalid-feedback">
First name required
</div>
<div [hidden]="!userFirstName.errors?.minlength" class="invalid-feedback">
Must be at least 2 characters
</div>
</div>
<div class="form-group">
<label>Last Name</label>
<input
type="text"
[(ngModel)]="user.lastName"
[ngClass]="{'is-invalid':userLastName.errors && userLastName.touched}"
class="form-control"
name="lastName"
#userLastName="ngModel"
required
minlength="2"
>
<div [hidden]="!userLastName.errors?.required" class="invalid-feedback">
Last name required
</div>
<div [hidden]="!userLastName.errors?.minlength" class="invalid-feedback">
Must be at least 2 characters
</div>
</div>
<div class="form-group">
<label>Email</label>
<input
type="email"
[(ngModel)]="user.email"
[ngClass]="{'is-invalid':userEmail.errors && userEmail.touched}"
class="form-control"
name="email"
#userEmail="ngModel"
required
pattern="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
>
<div [hidden]="!userEmail.errors?.required" class="invalid-feedback">
Email name required
</div>
<div [hidden]="!userEmail.errors?.pattern" class="invalid-feedback">
Email is not valid
</div>
</div>
<button [disabled]="!userForm.form.valid" class="btn btn-block mb-3">Add New User</button>
</form>
</div>
<ul class="list-unstyled" *ngIf="loaded && users?.length > 0">
<li class="card card-body mb-2" *ngFor="let user of users" [class.bg-light]="user.isActive">
<h3>{{ user.firstName }} {{ user.lastName }}
<small>
<button (click)="user.hide = !user.hide" class="btn btn-dark btn-sm">
<i [ngClass]="user.hide ? 'fa fa-plus' : 'fa fa-minus'"></i>
</button>
</small>
</h3>
<ul class="list-group" *ngIf="!user.hide">
<li class="list-group-item">Emial: {{ user.email }}</li>
<li class="list-group-item">Joined: {{ user.registered | date }}</li>
</ul>
</li>
</ul>
<h4 *ngIf="users?.length == 0">No Users Found</h4>
<h4 *ngIf="!loaded">Loading Users...</h4>
TS
import { Component, OnInit, ViewChild } from '@angular/core';
import { User } from '../../models/User';
@Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.css']
})
export class UsersComponent implements OnInit {
user: User = {
firstName: '',
lastName: '',
email: ''
}
users: User[];
showExtended: boolean = true;
loaded: boolean = false;
enableAdd: boolean = false;
showUserForm: boolean = false;
@ViewChild('userForm') form: any;
constructor() { }
ngOnInit() {
this.users = [
{
firstName: 'John',
lastName: 'Doe',
email: 'john@gmail.com',
isActive: true,
registered: new Date('01/02/2018 08:30:00'),
hide: true
},
{
firstName: 'Kevin',
lastName: 'Johnson',
email: 'kevin@yahoo.com',
isActive: false,
registered: new Date('03/11/2017 06:20:00'),
hide: true
},
{
firstName: 'Karen',
lastName: 'Williams',
email: 'karen@gmaial.com',
isActive: true,
registered: new Date('11/02/2016 10:30:00'),
hide: true
}
];
this.loaded = true;
}
onSubmit({value, valid}: {value: User, valid: boolean}) {
if(!valid){
console.log('Form is not valid');
} else {
value.isActive = true;
value.registered = new Date();
value.hide = true;
this.users.unshift(value);
this.form.reset();
}
}
}
Interface
export interface User {
firstName: string,
lastName: string,
email: string,
isActive?: boolean,
registered?: any,
hide?: boolean
}