Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Template-driven forms

Angular has two kinds of forms. Template-driven forms are described right in the HTML: each input is bound to a class field with the ngModel directive. They are simpler and fit small forms like the new post form well.

Adding FormsModule

The template-driven form directives live in FormsModule — add it to the component's imports. Let's rewrite the post form src/app/post-form/post-form.ts:

// src/app/post-form/post-form.ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
 
@Component({
  selector: 'app-post-form',
  imports: [FormsModule],
  templateUrl: './post-form.html',
})
export class PostForm {
  post = { title: '', body: '' };
}

ngModel

The ngModel directive in a two-way binding connects an input to a class property. The name attribute is required — the form identifies the field by it:

<!-- src/app/post-form/post-form.html -->
<input name="title" [(ngModel)]="post.title" placeholder="Title" />
<p>You typed: {{ post.title }}</p>

Start typing in the field — the text appears under it right away. And the other way round: if code changes post.title, the value in the field changes too.

Submitting the form

The ngSubmit event fires when the submit button or Enter is pressed. Angular prevents the page reload by itself:

<!-- src/app/post-form/post-form.html -->
<form (ngSubmit)="save()">
  <input name="title" [(ngModel)]="post.title" placeholder="Title" />
  <textarea name="body" [(ngModel)]="post.body" placeholder="Text"></textarea>
  <button type="submit">Publish</button>
</form>
// src/app/post-form/post-form.ts
import { Component, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { PostApi } from '../post-api';
 
@Component({
  selector: 'app-post-form',
  imports: [FormsModule],
  templateUrl: './post-form.html',
})
export class PostForm {
  private api = inject(PostApi);
  post = { title: '', body: '' };
  message = signal('');
 
  save() {
    this.api.createPost({ ...this.post }).subscribe((created) => {
      this.message.set(`Post #${created.id} created`);
    });
  }
}

Fill in the fields and click "Publish" — the request goes to JSONPlaceholder, and message becomes "Post #101 created" (show {{ message() }} under the form).

The form object

A template variable with the value ngForm gives access to the whole form:

<!-- src/app/post-form/post-form.html -->
<form #form="ngForm" (ngSubmit)="save(); form.reset()">
  ...
</form>
<pre>{{ form.value | json }}</pre>

form.value is an object with all fields keyed by their name, and form.reset() clears the form. For the json pipe add JsonPipe from @angular/common to imports.

Checkboxes and selects

ngModel works with any field:

<!-- src/app/post-form/post-form.html -->
<label><input type="checkbox" name="draft" [(ngModel)]="draft" /> Draft</label>
<select name="category" [(ngModel)]="category">
  <option value="news">News</option>
  <option value="howto">How-to</option>
</select>

The checkbox is bound to a boolean, the select to the string from the chosen option's value. Add the fields draft = false and category = 'news' to the class.

Common mistake

Forgetting the name attribute on a field inside <form>. Angular throws an error saying that ngModel inside a form tag must have a name attribute. Give every field with ngModel in a form a unique name.

Thanks for reading!

Donate to the site — we work hard for you.