Menu
ร—
   โฎ     
HTML CSS JAVASCRIPT SQL PYTHON JAVA PHP HOW TO W3.CSS C C++ C# BOOTSTRAP REACT MYSQL JQUERY EXCEL XML DJANGO NUMPY PANDAS NODEJS DSA TYPESCRIPT ANGULAR ANGULARJS GIT POSTGRESQL MONGODB ASP AI R GO KOTLIN SASS VUE GEN AI SCIPY CYBERSECURITY DATA SCIENCE INTRO TO PROGRAMMING BASH RUST

Angular Forms


Forms let users enter and edit data in your app.


Forms Essentials

  • Two approaches: Template-driven (HTML-first with [(ngModel)]) and Reactive (code-first with FormGroup/FormControl).
  • When to use: Template-driven for simple forms; Reactive for complex validation, dynamic fields, and testability.
  • Key concepts: A FormControl tracks a single input's value/state; a FormGroup groups controls by name.
  • Imports: FormsModule (template-driven) and ReactiveFormsModule (reactive).

<form #f="ngForm">
  <input name="name" [(ngModel)]="name">
</form>
  

form = new FormGroup({ name: new FormControl('') });

<form [formGroup]="form">
  <input formControlName="name">
</form>
  

Notes:

  • Related: See Data Binding for [(ngModel)] and property binding, Events for handling input, and Templates for interpolation.
  • Import FormsModule (template-driven) and ReactiveFormsModule (reactive).

Template-driven Forms

  • Quick to start and feels like plain HTML.
  • Bind with [(ngModel)] and unique name attributes.
  • Access overall state via exported ngForm (e.g., valid, touched).
  • Import FormsModule in standalone components.
<form #f="ngForm" (ngSubmit)="onSubmit()">
  <input name="name" [(ngModel)]="name" required minlength="3" #c="ngModel">
  <div *ngIf="c.invalid && (c.dirty || c.touched)">Invalid</div>
  <button [disabled]="f.invalid">Submit</button>
</form>

Example

import { bootstrapApplication } from '@angular/platform-browser';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, FormsModule],
  template: `
    <h3>Forms</h3>
    <form #f="ngForm" (ngSubmit)="onSubmit()">
      <label>
        Name:
        <input name="name" [(ngModel)]="name" placeholder="Enter your name">
      </label>
      <button type="submit">Submit</button>
    </form>
    <p>Value: {{ name }}</p>
    <p *ngIf="submitted">Submitted!</p>
  `
})
export class App {
  name = '';
  submitted = false;
  onSubmit() { this.submitted = true; }
}

bootstrapApplication(App);
<app-root></app-root>

Run Example ยป

Example explained

  • [(ngModel)]="name": Two-way binds the input to the name field.
  • #f="ngForm": Exports the form state (e.g., f.valid, f.invalid).
  • (ngSubmit)="onSubmit()": Handles submit using the component method.
  • Display: {{ name }} shows the current value; a flag shows the submitted state.

Notes:

  • Unique name required: Each control needs a unique name to register with ngForm.
  • Module imports: Template-driven forms require FormsModule (for standalone components, add it to imports).


HTML Form Elements in Angular

  • Text/Email/Number: Bind with [(ngModel)] or formControlName.
  • Textarea: Works like text inputs.
  • Checkbox: Boolean value via [(ngModel)] or formControlName.
  • Radio group: Share the same name; bind group with [(ngModel)] or formControlName. Use [value].
  • Select: Bind selected value; use [ngValue] when options are objects.
  • File input: Read files with (change) handler; do not two-way bind file objects.
<input name="email" type="email" [(ngModel)]="model.email">
<textarea name="bio" [(ngModel)]="model.bio"></textarea>
<label><input type="checkbox" name="agree" [(ngModel)]="model.agree"> Agree</label>
<label><input type="radio" name="color" [value]="'red'" [(ngModel)]="model.color"> Red</label>
<label><input type="radio" name="color" [value]="'blue'" [(ngModel)]="model.color"> Blue</label>
<select name="pet" [(ngModel)]="model.pet">
  <option [ngValue]="{ id: 1, name: 'Cat' }">Cat</option>
  <option [ngValue]="{ id: 2, name: 'Dog' }">Dog</option>
</select>
<input type="file" (change)="onFiles($event)">

Tip: For object options in <select>, use [ngValue] instead of value.

Radio with non-string values

  • Use [ngValue] to bind non-string values (numbers or objects) to radios.
<label><input type="radio" name="size" [ngValue]="1" [(ngModel)]="model.size"> Small</label>
<label><input type="radio" name="size" [ngValue]="2" [(ngModel)]="model.size"> Medium</label>

Select multiple

  • Add multiple and bind to an array; use [ngValue] for non-strings.
<select name="tags" [(ngModel)]="model.tags" multiple>
  <option [ngValue]="'news'">News</option>
  <option [ngValue]="'tech'">Tech</option>
  <option [ngValue]="'sports'">Sports</option>
</select>

Number inputs: coercion

  • Template-driven binds values as strings; convert in code if you need numbers.
<input type="number" name="age" [ngModel]="age" (ngModelChange)="age = $any($event)">

File input (multiple)

  • Read files with a (change) handler; do not two-way bind files.
<input type="file" multiple (change)="onFiles($event)">

compareWith for select of objects

  • Use [compareWith] when options are objects that may be re-created across renders.
<select name="pet" [(ngModel)]="model.pet" [compareWith]="byId">
  <option [ngValue]="{ id: 1, name: 'Cat' }">Cat</option>
  <option [ngValue]="{ id: 2, name: 'Dog' }">Dog</option>
</select>

byId = (a: any, b: any) => a?.id === b?.id;

Validation

  • Add rules like required, minlength, and email.
  • Show errors when invalid and the control is dirty or touched, or after submit.
  • Disable submit when the form is invalid.
<input name="email" [(ngModel)]="email" email required #e="ngModel">
<div *ngIf="e.invalid && (e.dirty || e.touched)">
  <small *ngIf="e.errors && e.errors['required']">Required</small>
  <small *ngIf="e.errors && e.errors['email']">Invalid email</small>
</div>

Example

import { bootstrapApplication } from '@angular/platform-browser';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, FormsModule],
  template: `
    <h3>Forms Validation</h3>
    <form #f="ngForm" (ngSubmit)="onSubmit()" novalidate>
      <label>
        Name:
        <input name="name" [(ngModel)]="model.name" required minlength="3" #name="ngModel">
      </label>
      <div *ngIf="name.invalid && (name.dirty || name.touched || submitted)" style="color:crimson">
        <small *ngIf="name.errors && name.errors['required']">Name is required.</small>
        <small *ngIf="name.errors && name.errors['minlength']">Name must be at least 3 characters.</small>
      </div>

      <label>
        Email:
        <input name="email" [(ngModel)]="model.email" email required #email="ngModel">
      </label>
      <div *ngIf="email.invalid && (email.dirty || email.touched || submitted)" style="color:crimson">
        <small *ngIf="email.errors && email.errors['required']">Email is required.</small>
        <small *ngIf="email.errors && email.errors['email']">Email must be valid.</small>
      </div>

      <button type="submit" [disabled]="f.invalid">Submit</button>
    </form>

    <p *ngIf="submitted">Submitted: {{ model | json }}</p>
  `
})
export class App {
  model = { name: '', email: '' };
  submitted = false;
  onSubmit() { this.submitted = true; }
}

bootstrapApplication(App);
<app-root></app-root>

Run Example ยป

Example explained

  • #name="ngModel": Exports the control state for the name input.
  • Errors: name.errors['required'] and name.errors['minlength'] drive specific messages.
  • When to show: Messages appear when the control is invalid and dirty || touched || submitted.
  • Disable submit: The button binds to f.invalid to prevent invalid submission.

Notes:

  • When to show errors: Gate messages behind dirty || touched || submitted so they don't flash too early.
  • Disable submit correctly: Bind to f.invalid (template) or form.invalid (reactive).

Reactive Forms

  • Build a tree of FormGroup/FormControl in code.
  • Bind the template with [formGroup] and formControlName.
  • Great for complex validation, conditional fields, and dynamic forms.
  • Create controls with FormBuilder and Validators.
form = this.fb.group({
  name: ['', [Validators.required, Validators.minLength(3)]],
  email: ['', [Validators.required, Validators.email]],
});

<form [formGroup]="form">
  <input formControlName="name">
  <input formControlName="email">
</form>

Example

import { bootstrapApplication } from '@angular/platform-browser';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule],
  template: `
    <h3>Reactive Forms</h3>
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <label>
        Name
        <input formControlName="name" placeholder="Your name">
      </label>
      <div *ngIf="form.controls.name.invalid && (form.controls.name.dirty || form.controls.name.touched || submitted)" style="color:crimson">
        <small *ngIf="form.controls.name.errors && form.controls.name.errors['required']">Name is required.</small>
        <small *ngIf="form.controls.name.errors && form.controls.name.errors['minlength']">Min 3 characters.</small>
      </div>

      <label>
        Email
        <input formControlName="email" placeholder="you@example.com">
      </label>
      <div *ngIf="form.controls.email.invalid && (form.controls.email.dirty || form.controls.email.touched || submitted)" style="color:crimson">
        <small *ngIf="form.controls.email.errors && form.controls.email.errors['required']">Email is required.</small>
        <small *ngIf="form.controls.email.errors && form.controls.email.errors['email']">Email must be valid.</small>
      </div>

      <label>
        <input type="checkbox" formControlName="newsletter">
        Subscribe to newsletter
      </label>

      <button type="submit" [disabled]="form.invalid">Submit</button>
    </form>

    <p>Status: {{ form.status }}</p>
    <p>Value: {{ form.value | json }}</p>
    <p *ngIf="submitted" style="color: seagreen;">Submitted!</p>
  `
})
export class App {
  fb = new FormBuilder();
  submitted = false;
  form = this.fb.group({
    name: ['', [Validators.required, Validators.minLength(3)]],
    email: ['', [Validators.required, Validators.email]],
    newsletter: [false],
  });

  onSubmit() { this.submitted = true; }
}

bootstrapApplication(App);
<app-root></app-root>

Run Example ยป

Example explained

  • [formGroup]="form": Binds the form element to the FormGroup instance.
  • formControlName: Wires inputs to named controls (name, email, newsletter).
  • Validators: Created with FormBuilder and Validators; error messages read control errors.
  • Submit: Disables the button when form.invalid; sets a submitted flag on submit.

Notes:

  • Don't mix paradigms: Avoid [(ngModel)] on controls that also use formControlName.
  • Update via API: Use setValue/patchValue and validator methods rather than mutating control objects directly.
  • Module imports: Reactive forms require ReactiveFormsModule.


ร—

Contact Sales

If you want to use W3Schools services as an educational institution, team or enterprise, send us an e-mail:
sales@w3schools.com

Report Error

If you want to report an error, or if you want to make a suggestion, send us an e-mail:
help@w3schools.com

W3Schools is optimized for learning and training. Examples might be simplified to improve reading and learning. Tutorials, references, and examples are constantly reviewed to avoid errors, but we cannot warrant full correctness of all content. While using W3Schools, you agree to have read and accepted our terms of use, cookie and privacy policy.

Copyright 1999-2025 by Refsnes Data. All Rights Reserved. W3Schools is Powered by W3.CSS.