Template-Driven Forms
Angular has two distinct, official approaches to forms — template-driven (this chapter) and reactive (next chapter). Template-driven forms keep most of the form's setup in the HTML template itself, built on the ngModel two-way binding from Chapter 3. They're the quicker option for simpler forms, and the closer fit to how React's controlled inputs felt.
The Setup — ngForm and ngModel Together
Three pieces work together here. #signupForm="ngForm" is a template reference variable — it captures the form's auto-created NgForm object, giving access to its overall validity (signupForm.invalid). (ngSubmit) fires the handler on submit, already preventing the native page reload that React needed preventDefault() for (Fundamentals Chapter 4). Each [(ngModel)] input needs a name attribute so the form can track it as a named control.
Validation Through Directives
Validation is declared as plain attributes on the inputs — required, email, minlength, maxlength, pattern. Angular wires these standard-looking HTML attributes into its own validation system, tracking each control's validity and the overall form's. [disabled]="signupForm.invalid" then disables the submit button until every validator passes — declarative validation with no JavaScript validation code written at all.
Showing Validation Messages
A per-input reference variable — #email="ngModel" — captures that single control's state, exposing flags like invalid, valid, touched (the user has focused then left it), and dirty (the value has changed). Combining invalid && touched with the @if from Chapter 4 shows an error message only after the user has actually interacted with the field — not immediately on an untouched, empty form.
touched waits until they've left a field; dirty waits until they've changed it. React projects had to track this kind of "has the user interacted yet" state manually — Angular's forms provide it built in, on every control.
[(ngModel)] control without a name attribute (or with a duplicate one) throws a runtime error — the form uses name as the key to register and track each control. This is easy to forget when copying the simpler standalone ngModel usage from Chapter 3, which worked without a name because it wasn't inside an ngForm.
| Angular template-driven | React controlled-form equivalent |
|---|---|
| [(ngModel)]="model.x" | value + onChange |
| (ngSubmit)="onSubmit()" | onSubmit + preventDefault() |
| required / email / minlength | Hand-written validation logic |
| control.touched / .dirty | Manually tracked "interacted" state |
Coding Challenges
Build a template-driven form with name and email fields (both required, email also using the email validator), a model object, and a submit handler that logs the model — with the submit button disabled while the form is invalid.
📄 View solutionAdd per-field validation messages to the form, each shown only when that field is both invalid and touched, using a per-input #ref="ngModel" reference variable and @if.
📄 View solutionBuild a template-driven "create account" form with a username (required, minlength 3) and a password (required, minlength 8), showing the specific reason a field is invalid (e.g. "too short" vs "required") by checking the control's errors.
📄 View solutionChapter 8 Quick Reference
- Template-driven forms build on
[(ngModel)]and require FormsModule - #form="ngForm" — a template ref capturing the whole form's state (e.g.
form.invalid) - (ngSubmit) — submit handler; already prevents the native page reload
- Validation is declared as attributes:
required,email,minlength,pattern - #field="ngModel" — a per-control ref exposing
invalid/touched/dirty/errors - Every
ngModelcontrol inside a form needs a uniquenameattribute - Next chapter: reactive forms — the same goals, but defined in the component class instead