Angular 31: Template-Driven Forms Deep Dive
easy⏱ 5 mincourseangular
FormsModule + [(ngModel)]: the form model is inferred from the DOM
Import FormsModule into a standalone component's imports array, then bind each input with [(ngModel)]="someProperty" and a matching name attribute — the name is what Angular uses as the key inside the generated FormGroup. There is no new FormGroup(...) anywhere in the class; the shape of the form lives entirely in the template.
@Component({
selector: 'app-login-form',
standalone: true,
imports: [FormsModule],
template: `
<form #loginForm="ngForm">
<input name="email" [(ngModel)]="credentials.email" />
<input name="password" type="password" [(ngModel)]="credentials.password" />
</form>
`,
})
export class LoginFormComponent {
credentials = { email: '', password: '' };
}
#loginForm="ngForm": a live handle on the hidden FormGroup
#loginForm="ngForm" attaches a template reference variable to the directive Angular puts on every <form> tag automatically (NgForm). Through it you can read loginForm.valid, loginForm.value, loginForm.submitted, and per-control state such as loginForm.controls['email']?.touched — all without injecting anything into the component class.
<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm.value)">
<!-- fields here -->
<button [disabled]="!loginForm.valid">Log in</button>
</form>
<pre>{{ loginForm.value | json }}</pre>
Template-driven vs. Reactive: when to reach for which
Template-driven forms shine for small, simple forms where the template-first mental model is convenient — think a quick search box or a login form. Reactive forms (lessons 32-34) trade a bit of template verbosity for a form model that lives entirely in TypeScript, which pays off fast once you need dynamic fields, complex cross-field validation, or unit tests that don't need a rendered template.