typescript
60 lines · 10 steps
A reactive signup form in Angular
A standalone Angular component builds a validated form, posts it, and folds server-side validation errors back onto the right fields.
Explained by
highlit
1import { Component, inject } from '@angular/core';
2import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
3import { HttpClient, HttpErrorResponse } from '@angular/common/http';
4import { finalize } from 'rxjs';
5
6interface ValidationError {
7 message: string;
8 errors: Record<string, string[]>;
9}
10
11@Component({
12 selector: 'app-signup-form',
13 standalone: true,
14 imports: [ReactiveFormsModule],
15 templateUrl: './signup-form.component.html',
16})
17export class SignupFormComponent {
18 private readonly fb = inject(FormBuilder);
19 private readonly http = inject(HttpClient);
20
21 submitting = false;
22
23 readonly form = this.fb.group({
24 name: ['', Validators.required],
25 email: ['', [Validators.required, Validators.email]],
26 password: ['', [Validators.required, Validators.minLength(8)]],
27 });
28
29 submit(): void {
30 if (this.form.invalid) {
31 this.form.markAllAsTouched();
32 return;
33 }
34
35 this.submitting = true;
36 this.http
37 .post('/api/users', this.form.getRawValue())
38 .pipe(finalize(() => (this.submitting = false)))
39 .subscribe({
40 next: () => this.form.reset(),
41 error: (err: HttpErrorResponse) => this.applyServerErrors(err),
42 });
43 }
44
45 private applyServerErrors(err: HttpErrorResponse): void {
46 if (err.status !== 422) {
47 return;
48 }
49
50 const body = err.error as ValidationError;
51 for (const [field, messages] of Object.entries(body.errors ?? {})) {
52 const control = this.form.get(field);
53 if (!control) {
54 continue;
55 }
56 control.setErrors({ ...control.errors, server: messages[0] });
57 control.markAsTouched();
58 }
59 }
60}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Reactive forms let you declare validation rules up front and check validity before any network call.
- 2A finalize operator guarantees cleanup like resetting a loading flag whether the request succeeds or fails.
- 3Mapping a 422 response back onto individual controls turns server validation into inline field errors.
Related explainers
typescript
import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; import localeDe from '@angular/common/locales/de';
Locale-aware bootstrapping in Angular
i18n
localization
dependency-injection
Intermediate
8 steps
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
typescript
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as Joi from 'joi';
Validating env config at boot in NestJS
configuration
schema-validation
environment-variables
Intermediate
8 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-reactive-signup-form-in-angular-explained-typescript-c3e1/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.