typescript
58 lines · 10 steps
Building a signup stepper in Angular
A standalone Angular component pairs signals with a nested reactive form to drive a multi-step signup wizard.
Explained by
highlit
1import { Component, computed, inject, signal } from '@angular/core';
2import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
3import { CommonModule } from '@angular/common';
4
5@Component({
6 selector: 'app-signup-stepper',
7 standalone: true,
8 imports: [CommonModule, ReactiveFormsModule],
9 templateUrl: './signup-stepper.component.html',
10})
11export class SignupStepperComponent {
12 private fb = inject(FormBuilder);
13
14 readonly steps = ['account', 'profile', 'billing'] as const;
15 readonly currentStep = signal(0);
16
17 readonly form = this.fb.group({
18 account: this.fb.group({
19 email: ['', [Validators.required, Validators.email]],
20 password: ['', [Validators.required, Validators.minLength(8)]],
21 }),
22 profile: this.fb.group({
23 firstName: ['', Validators.required],
24 lastName: ['', Validators.required],
25 }),
26 billing: this.fb.group({
27 cardNumber: ['', [Validators.required, Validators.pattern(/^\d{16}$/)]],
28 cvc: ['', [Validators.required, Validators.pattern(/^\d{3}$/)]],
29 }),
30 });
31
32 readonly isLastStep = computed(() => this.currentStep() === this.steps.length - 1);
33
34 private activeGroup() {
35 return this.form.get(this.steps[this.currentStep()])!;
36 }
37
38 next(): void {
39 const group = this.activeGroup();
40 if (group.invalid) {
41 group.markAllAsTouched();
42 return;
43 }
44 if (!this.isLastStep()) {
45 this.currentStep.update((i) => i + 1);
46 }
47 }
48
49 back(): void {
50 this.currentStep.update((i) => Math.max(0, i - 1));
51 }
52
53 submit(): void {
54 if (this.form.valid) {
55 this.form.disable();
56 }
57 }
58}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Nesting form groups by step lets you validate one section at a time while keeping a single source of truth.
- 2Angular signals model wizard state declaratively, so derived facts like the last step recompute automatically.
- 3Gating step advancement on per-group validity gives users focused feedback instead of a wall of errors.
Related explainers
javascript
import { useReducer, useCallback } from 'react'; function historyReducer(state, action) { const { past, present, future } = state;
Undo/redo form state with a React reducer
undo-redo
reducer
immutability
Intermediate
10 steps
typescript
type Middleware<TIn, TOut> = (ctx: TIn) => Promise<TOut> | TOut; class Pipeline<TIn, TOut> { private constructor(private readonly run: Middleware<TIn, TOut>) {}
A type-safe async middleware pipeline
generics
type-safety
middleware
Advanced
9 steps
typescript
type Countdown = { days: number; hours: number; minutes: number;
Building a self-stopping countdown timer
date-math
closures
timers
Intermediate
9 steps
python
from django.core.cache import cache from django.core.cache.utils import make_template_fragment_key from django.db.models.signals import post_save, post_delete from django.dispatch import receiver
Busting template fragment caches in Django
caching
signals
cache-invalidation
Intermediate
4 steps
typescript
export function isValidCardNumber(input: string): boolean { const digits = input.replace(/[\s-]/g, ""); if (!/^\d{12,19}$/.test(digits)) {
Validating card numbers with the Luhn check
luhn-algorithm
checksum
input-validation
Intermediate
7 steps
typescript
interface UserAgentInfo { browser: { name: string; version: string }; os: { name: string; version: string }; device: 'mobile' | 'tablet' | 'desktop';
Parsing a user-agent string with ordered rules
regex
parsing
pattern-matching
Intermediate
9 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/building-a-signup-stepper-in-angular-explained-typescript-db12/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.