Skip to content

Latest commit

 

History

History
346 lines (266 loc) · 9.02 KB

File metadata and controls

346 lines (266 loc) · 9.02 KB

Angular 20 Registration Wizard

A production-ready, multi-step registration form demonstrating modern Angular architecture, clean code principles, and enterprise-level patterns.

Angular TypeScript TailwindCSS License: MIT


📋 Project Overview

This project implements a production-ready 4-step registration wizard built with Angular 20 and modern frontend architecture principles. It focuses on scalability, maintainability, and real-world use cases, featuring state persistence, robust form validation, internationalization, and a flexible, configuration-driven design.

✨ Key Features

Core Functionality

  • Dynamic Step Configuration - Add/remove/reorder steps through configuration
  • State Persistence - Automatic IndexedDB save/restore with fallback
  • Mock API Support - Complete mock services with realistic delays
  • Form Validation - Real-time validation with custom validators
  • File Upload - Drag & drop with preview and validation
  • Internationalization - Persian/English support with ngx-translate
  • Responsive Design - Mobile-first approach with TailwindCSS

Advanced Features

  • ControlValueAccessor Pattern - Reusable form components
  • OnPush Change Detection - Optimized performance
  • Signals & Computed - Modern reactive state management
  • Dynamic Component Loading - Configuration-driven step rendering
  • Error Handling - Centralized FormErrorService with i18n
  • Navigation Guards - Smart step validation and navigation rules

🚀 Quick Start

Prerequisites

  • Node.js 18.x or higher
  • npm 9.x or higher

Installation & Running

# Clone repository
git clone https://github.com/yourusername/angular-registration-wizard.git
cd angular-registration-wizard

# Install dependencies
npm install

# Run with mock API (default)
ng serve

# Run with real API
# 1. Update src/environments/environment.ts
# 2. Set useMockData: false
# 3. Configure apiUrl
ng serve

# Open http://localhost:4200

Build for Production

# Production build
ng build --configuration production

# Output in dist/ folder
# Deploy to Netlify, Vercel, or any static hosting

🎯 Architecture & Implementation

1. Dynamic Step Configuration System

Problem Solved: Adding/removing/reordering steps required changes across multiple files (enum, template @switch, service validations).

Solution: Single configuration file with dynamic component loading.

// StepRegistryService - Single source of truth
private readonly stepConfigs: StepConfig[] = [
  {
    id: 'personal-info',
    order: 0,
    title: 'STEPS.PERSONAL_INFO',
    component: PersonalInfoComponent,
    isRequired: true,
    canNavigateBack: false
  },
  // Add new step - just add to array!
  {
    id: 'payment-info',
    order: 2,
    title: 'STEPS.PAYMENT',
    component: PaymentInfoComponent,
    isRequired: true,
    canNavigateBack: true
  }
];

Benefits:

  • No template modifications needed
  • Type-safe with StepConfig interface
  • Easy to maintain and scale
  • Single responsibility principle

2. ControlValueAccessor Pattern

All form controls implement Angular's ControlValueAccessor for seamless Reactive Forms integration:

<app-text-input 
  formControlName="firstName"
  label="First Name"
  [required]="true"
/>

Features:

  • Works seamlessly with Reactive Forms
  • Automatic validation integration
  • Consistent error display with i18n
  • OnPush change detection optimized
  • Reusable across projects

3. State Persistence Strategy

// Automatic save on every change
updatePersonalInfo()  Signal Update  Effect  IndexedDB

// Automatic restore on page load
APP_INITIALIZER  loadInitialState()  Restore from IndexedDB

Edge Cases Handled:

  • Browser doesn't support IndexedDB (graceful fallback)
  • Quota exceeded errors (clear old data)
  • Corrupted data recovery (reset to defaults)
  • Race conditions (debounced saves)

4. Mock API Architecture

Toggle Between Mock and Real:

// environment.ts
export const environment = {
  production: false,
  useMockData: true  // false for production
};

// app.config.ts - Conditional provider
{
  provide: LocationHttpService,
  useClass: environment.useMockData 
    ? LocationMockHttpService 
    : LocationHttpService
}

Benefits:

  • Zero backend dependency
  • Realistic network delays (300ms simulated)
  • Same interface as real API
  • Easy testing and demos

5. Form Validation Strategy

Validator Implementation Use Case
Persian Text Custom regex /^[\u0600-\u06FF\s]+$/ Names, addresses
National ID Checksum algorithm (Luhn) Iranian 10-digit ID
No Whitespace Custom validator Prevents only spaces
File Type MIME type check JPG/PNG validation
File Size Byte comparison Max 5MB

6. Internationalization

// Switch language dynamically
this.translate.use('en'); // or 'fa'

// In templates
{{ 'ERRORS.REQUIRED' | translate }}
{{ 'ERRORS.MINLENGTH' | translate: {requiredLength: 5} }}

Structure:

public/i18n/
├── fa.json  # Persian (default)
└── en.json  # English

🎨 Design Decisions & Rationale

Why IndexedDB over LocalStorage?

  • Capacity: 50MB+ vs 5MB
  • Structure: Stores objects directly (no JSON parsing)
  • Performance: Async, non-blocking operations
  • Future-proof: Can handle large files if needed

Why Signals over RxJS?

  • Simpler: Easier to understand and maintain
  • Performance: Fine-grained reactivity, better change detection
  • Modern: Angular's recommended approach
  • Note: RxJS still used for HTTP (appropriate use case)

Why ControlValueAccessor?

  • Consistency: All form controls work identically
  • Reusability: Use across multiple forms/projects
  • Integration: Built-in Reactive Forms support
  • Type Safety: Full TypeScript integration

Why OnPush Everywhere?

  • Performance: 50-90% fewer change detection cycles
  • Best Practice: Modern Angular default strategy
  • Scalability: Critical for large applications
  • Explicit: Forces intentional state updates

🔧 Customization Guide

Add a New Step

// 1. Create component
ng g c features/registration-stepper/components/payment-info

// 2. Implement IStepForm interface
export class PaymentInfoComponent implements IStepForm {
  isValid(): boolean {
    return this.form.valid;
  }
}

// 3. Add to StepRegistryService
{
  id: 'payment-info',
  order: 2,  // Insert at desired position
  title: 'STEPS.PAYMENT',
  component: PaymentInfoComponent,
  isRequired: true,
  canNavigateBack: true
}

// 4. Add translations
// public/i18n/fa.json
"STEPS": {
  "PAYMENT": "اطلاعات پرداخت"
}

// public/i18n/en.json
"STEPS": {
  "PAYMENT": "Payment Information"
}

That's it! No template changes, no enum updates, no additional logic.

Add Custom Validator

// src/app/core/validators/custom.validator.ts
export function emailDomainValidator(allowedDomain: string): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    if (!control.value) return null;
    
    const email = control.value.toLowerCase();
    const isValid = email.endsWith(`@${allowedDomain}`);
    
    return isValid ? null : { emailDomain: { allowedDomain } };
  };
}

// Add translation
// public/i18n/fa.json
"ERRORS": {
  "EMAILDOMAIN": "ایمیل باید از دامنه {{allowedDomain}} باشد"
}

// Usage in form
this.form = this.fb.group({
  email: ['', [Validators.email, emailDomainValidator('company.com')]]
});

Change Theme Colors

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: {
          50: '#eff6ff',
          500: '#3b82f6',
          600: '#2563eb',
          700: '#1d4ed8',
        }
      }
    }
  }
}

🤝 Contributing

Contributions are welcome! Please:

  1. Fork the project
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'feat: Add amazing feature')
  4. Push to branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Code Style

  • Follow Angular Style Guide
  • Use TypeScript strict mode
  • Write meaningful commit messages

📄 License

This project is licensed under the MIT License - see LICENSE file for details.

📬 Contact

Mehdi Hadizadeh


If you found this project helpful, please give it a star!