Signal forms in Angular
Angular form signals are a powerful tool that enables the creation of interactive web applications with elegance and simplicity. By embracing a reactive approach to data handling, they ensure instant updates to the user interface as form values change. This feature allows developers to effortlessly track form states and manage their behavior, resulting in a more predictable and efficient development process. With Angular form signals, you can build highly responsive and dynamic applications, enhancing user interaction with your services while maintaining clean and maintainable code.
In this lesson, we will build a user form using Signal Forms. Through a practical example, we will cover:
- creating a form model with
signal(); - connecting the model to a form using
form(); - adding built-in validators with
required(); - creating custom validation rules with
validate(); - submitting the form and handling user input.
By the end of the lesson, you will see how Signal Forms can make your code more readable, type-safe, and easier to maintain compared to the traditional Reactive Forms approach.
users.component.ts
import {Component, injectAsync, input, InputSignal, signal} from '@angular/core';
import {form, FormField, required, validate} from '@angular/forms/signals';
import {FormsModule} from '@angular/forms';
import {JsonPipe} from '@angular/common';
interface UserForm {
userName: string;
role: 'admin' | 'editor' | 'viewer' | 'moderator';
}
@Component({
selector: 'app-users',
imports: [
FormField,
FormsModule,
JsonPipe
],
templateUrl: './users.component.html',
styleUrl: './users.component.css',
})
export class UsersComponent {
public users: InputSignal<string[]> = input<string[]>([]);
protected loginFormModel = signal<UserForm>({
userName: '',
role: 'admin',
})
protected loginForm = form(this.loginFormModel, (schema) => {
required(schema.userName, {
message: 'User name is required',
error: 'userNameRequired'
}),
validate(schema.userName, ({value}) => {
if(value().trim().length > 5) {
return {
kind: 'tooLong',
message: 'Too long username'
}
}
return null;
})
})
protected usersService = injectAsync(() =>
import('../users.service').then(s => s.UsersService));
protected async updateUsers() {
const users = await this.usersService();
users.updateUsers();
}
protected submitForm(event: Event) {
event.preventDefault();
console.log(this.loginFormModel());
console.log(this.loginForm.userName().valid());
}
}
users.component.html
<p>users works!</p>
<ul>
@for (user of users(); track user) {
<li>{{user}}</li>
}
</ul>
<button (click)="updateUsers()">Update users</button>
<form (submit)="submitForm($event)">
<p>
<input type="text" [formField]="loginForm.userName">
</p>
@if(loginForm.userName().touched() && loginForm.userName().invalid()) {
<p>Username is invalid</p>
<p>{{loginForm.userName().errors() | json}}</p>
}
<p>
</p>
<p>
<select>
<option value="admin">Admin</option>
<option value="editor">Editor</option>
<option value="viewer">Viewer</option>
<option value="moderator">Moderator</option>
</select>
</p>
<button>Submot form</button>
</form>
<hr>
Username: {{loginForm.userName().value() | json}}
Username touched: {{loginForm.userName().touched()}}
Username valid: {{loginForm.userName().valid()}}
Role: {{loginForm.role().value() | json}}
0 Comments