In this article, we’ll explore a real-world use case of the Memento Design Pattern using a multi-step form (stepper) in Angular. This pattern helps manage state across steps, allowing users to go back and forth while preserving previous data snapshots.
📘 What is the Memento Pattern?
The Memento Pattern is a behavioral design pattern that allows you to save and restore the previous state of an object without exposing its implementation details. It’s often used in scenarios like undo/redo, form state management, or editor history.
🧩 Components of the Memento Pattern
The pattern consists of three main components:
- Originator: The object whose state needs to be saved and restored.
- Memento: A snapshot of the Originator's state.
- Caretaker: Manages and stores the history of Mementos but does not modify or access their internal state.
💡 Real Case: Stepper Form with Snapshot History
Imagine a multi-step checkout form (e.g., shipping info, payment details, confirmation). We want to:
- Capture the form state after each step.
- Allow the user to go back and edit previous steps.
- Restore the state exactly as it was.
🛠️ Memento Implementation in Angular
1. Create the Memento Classes
// memento.ts
export class FormMemento {
constructor(public readonly state: any) {}
}
export class FormOriginator {
private state: any = {};
setState(state: any) {
this.state = { ...state };
}
save(): FormMemento {
return new FormMemento(this.state);
}
restore(memento: FormMemento) {
this.state = { ...memento.state };
}
getState(): any {
return this.state;
}
}
2. Caretaker Service to Store Snapshots
// memento.service.ts
import { Injectable } from '@angular/core';
import { FormMemento } from './memento';
@Injectable({ providedIn: 'root' })
export class MementoService {
private history: FormMemento[] = [];
addSnapshot(memento: FormMemento) {
this.history.push(memento);
}
getSnapshot(step: number): FormMemento | null {
return this.history[step] || null;
}
clear() {
this.history = [];
}
}
3. Stepper Component Example
// stepper.component.ts
import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { FormOriginator } from './memento';
import { MementoService } from './memento.service';
@Component({
selector: 'app-stepper',
templateUrl: './stepper.component.html'
})
export class StepperComponent {
step = 0;
form: FormGroup;
originator = new FormOriginator();
constructor(private fb: FormBuilder, private caretaker: MementoService) {
this.form = this.fb.group({
name: [''],
address: [''],
payment: ['']
});
}
nextStep() {
this.originator.setState(this.form.value);
this.caretaker.addSnapshot(this.originator.save());
this.step++;
}
previousStep() {
this.step--;
const snapshot = this.caretaker.getSnapshot(this.step);
if (snapshot) {
this.originator.restore(snapshot);
this.form.setValue(this.originator.getState());
}
}
submit() {
console.log('Final state:', this.form.value);
}
}
4. Template Example
<!-- stepper.component.html -->
<form [formGroup]="form">
<div *ngIf="step === 0">
<label>Name: <input formControlName="name" /></label>
</div>
<div *ngIf="step === 1">
<label>Address: <input formControlName="address" /></label>
</div>
<div *ngIf="step === 2">
<label>Payment: <input formControlName="payment" /></label>
</div>
<div class="buttons">
<button type="button" (click)="previousStep()" [disabled]="step === 0">Back</button>
<button type="button" (click)="nextStep()" *ngIf="step < 2">Next</button>
<button type="submit" (click)="submit()" *ngIf="step === 2">Submit</button>
</div>
</form>
✅ Benefits of This Approach
- Maintains form state cleanly across steps.
- Encapsulates logic for state saving/restoring.
- Easy to extend (e.g., with undo/redo or validation snapshots).
- Promotes separation of concerns via the Memento pattern components.