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
typescript
type Change = | { kind: "added"; path: string; value: unknown } | { kind: "removed"; path: string; value: unknown } | { kind: "updated"; path: string; from: unknown; to: unknown };
Building a recursive deep-diff in TypeScript
recursion
discriminated-unions
type-guards
Intermediate
9 steps
typescript
import { DynamicModule, Module, Provider } from '@nestjs/common'; import Redis, { RedisOptions } from 'ioredis'; export const REDIS_CLIENT = Symbol('REDIS_CLIENT');
Building a dynamic Redis module in NestJS
dependency-injection
dynamic-modules
provider-tokens
Intermediate
8 steps
typescript
type Operator = "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE"; type Row = Record<string, unknown>;
A type-safe SQL query builder in TypeScript
builder pattern
generics
method chaining
Intermediate
8 steps
typescript
import { Directive, ElementRef, HostListener, Input, OnDestroy, OnInit } from '@angular/core'; @Directive({ selector: '[appTrapFocus]',
Building a focus-trap directive in Angular
accessibility
focus-management
dom
Intermediate
9 steps
typescript
type AsyncFn<A extends unknown[], R> = (...args: A) => Promise<R>; interface MemoizeOptions<A extends unknown[]> { keyFn?: (...args: A) => string;
Memoizing async functions with TTL in TypeScript
memoization
generics
promises
Advanced
8 steps
typescript
import { Injectable, Scope, Inject } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { Request } from 'express';
Request-scoped context service in NestJS
dependency-injection
request-scope
immutability
Intermediate
8 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.