DEV Community

Neweraofcoding
Neweraofcoding

Posted on

Multi-Step Form in Angular—Basic Implementation

Building a Multi-Step Form in Angular: A Clean, Scalable Approach
Forms are everywhere—from onboarding flows to checkout pages—and sometimes, they get long. A multi-step form (aka form wizard) offers a better user experience by breaking down complex forms into digestible steps. In this post, I’ll walk through a clean, scalable way to implement a multi-step form using Angular.

Why Multi-Step Forms?
Multi-step forms reduce cognitive load and allow users to focus on one section at a time. They’re especially useful for:

Registration or onboarding processes
Surveys or feedback flows
Multi-part data entry (e.g., shipping → billing → confirmation)
Key Concepts Used
For this implementation, I relied on Angular’s core strengths:

Reactive Forms: For robust control over form data and validation
Step Components: Each form step is its own Angular component
Service-Based State Management: A singleton service holds form data across steps
Routing or Conditional Rendering: Navigate between steps using router or local state
How It Works
The flow is simple:

Step Components: Each step has its own form group and validation.
Form Data Service: Acts as a shared store, aggregating data from each step.
Navigation: Controlled through a parent component that tracks the current step.
Submit: On the final step, all form groups are merged and submitted as one payload.
What You Gain
Scalability: Easily add/remove steps
Separation of Concerns: Each step is modular
Reusability: Use the same logic for different workflows

Angular’s component architecture and reactive forms make it ideal for structured form workflows. Whether you’re building a simple sign-up wizard or a complex onboarding flow, a multi-step form approach keeps the UX smooth and the code clean.

Angular structure (component layout)
Step navigation logic
Form validation
Final submission

Project Structure
src/
├── app/
│ ├── multi-step-form/
│ │ ├── multi-step-form.component.ts
│ │ ├── multi-step-form.component.html
│ │ ├── step-one.component.ts
│ │ ├── step-two.component.ts
│ │ ├── step-three.component.ts
│ ├── form-data.service.ts
│ ├── app.module.ts

Form Data Service (form-data.service.ts)
This keeps track of form data across steps:

import { Injectable } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Injectable({ providedIn: 'root' })
export class FormDataService {
  private form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      name: [''],
      email: [''],
      age: ['']
    });
  }

  getForm(): FormGroup {
    return this.form;
  }

  setFormValues(values: any) {
    this.form.patchValue(values);
  }

  getFormValues(): any {
    return this.form.value;
  }
}
Enter fullscreen mode Exit fullscreen mode

Multi-Step Form Container (multi-step-form.component.ts)

import { Component } from '@angular/core';
import { FormDataService } from '../form-data.service';

@Component({
  selector: 'app-multi-step-form',
  templateUrl: './multi-step-form.component.html'
})
export class MultiStepFormComponent {
  step = 1;

  constructor(public formDataService: FormDataService) {}

  nextStep() {
    if (this.step < 3) this.step++;
  }

  prevStep() {
    if (this.step > 1) this.step--;
  }

  submit() {
    console.log('Submitted Data:', this.formDataService.getFormValues());
  }
}
Enter fullscreen mode Exit fullscreen mode

Parent HTML (multi-step-form.component.html)

<div class="form-container">
  <ng-container [ngSwitch]="step">
    <app-step-one *ngSwitchCase="1"></app-step-one>
    <app-step-two *ngSwitchCase="2"></app-step-two>
    <app-step-three *ngSwitchCase="3"></app-step-three>
  </ng-container>

  <div class="nav-buttons">
    <button (click)="prevStep()" [disabled]="step === 1">Back</button>
    <button *ngIf="step < 3" (click)="nextStep()">Next</button>
    <button *ngIf="step === 3" (click)="submit()">Submit</button>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Step Components Example (step-one.component.ts)

import { Component, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormDataService } from '../form-data.service';

@Component({
  selector: 'app-step-one',
  template: `
    <form [formGroup]="form">
      <label>Name:</label>
      <input formControlName="name" />
    </form>
  `
})
export class StepOneComponent implements OnInit {
  form: FormGroup;

  constructor(private formData: FormDataService) {}

  ngOnInit() {
    this.form = this.formData.getForm();
  }
}
Enter fullscreen mode Exit fullscreen mode

Repeat the same pattern for step-two and step-three, binding to fields like email and age.

App Module (app.module.ts)
Make sure to import the required modules:

import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { MultiStepFormComponent } from './multi-step-form/multi-step-form.component';
import { StepOneComponent } from './multi-step-form/step-one.component';
import { StepTwoComponent } from './multi-step-form/step-two.component';
import { StepThreeComponent } from './multi-step-form/step-three.component';

@NgModule({
  declarations: [
    AppComponent,
    MultiStepFormComponent,
    StepOneComponent,
    StepTwoComponent,
    StepThreeComponent
  ],
  imports: [BrowserModule, ReactiveFormsModule],
  bootstrap: [AppComponent]
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Result
This gives you a modular, reactive, multi-step form that:

Preserves state across steps
Validates each step separately
Submits combined data in the end

Top comments (0)