DEV Community

GOWTHAM THEJASWI
GOWTHAM THEJASWI

Posted on

2

Simplifying Dynamic Object Creation in Angular 18 with the Factory Pattern

 In this blog, we'll explore how to implement a generic factory design pattern in Angular 18, using a transportation service booking system as an example with a styled flow diagram with Mermaid.
The Factory Design Pattern is one of the most popular and widely used design patterns in software development. It provides a way to create objects without specifying their concrete classes.

What Is the Factory Design Pattern?

The Factory Design Pattern defines an interface for creating objects, but lets the subclasses alter the type of objects that will be created. This approach increases code flexibility and reduces dependencies on specific implementations.

Use Case: Transportation Service Booking

Imagine an application where users can book transportation services via different modes such as car, bike, bus, or train. Instead of directly instantiating the transportation services in components, we can use a factory design pattern to dynamically create and manage these service objects.

Image description

Implementation in Angular 18

1. Define an Interface for Transportation Services

This interface will serve as the blueprint for all transportation modes.

export interface TransportationService {
  book(from: string, to: string): string;
  calculateCost(from: string, to: string, rate: number): number;
  getDistance(from: string, to: string): number;
}
Enter fullscreen mode Exit fullscreen mode

2. Create Specific Transportation Services

Create standalone components for each transportation mode and implement the TransportationService interface.

Car Service Component

import { Injectable } from '@angular/core';
import { TransportationService } from './transportation.service';

@Injectable({ providedIn: 'root' })
export class CarService implements TransportationService {
  rates =10;
  book(from: string, to: string): string {
    const cost = this.calculateCost(from, to);
    return `Car booked successfully! From: ${from}, To: ${to}, Cost: $${cost}`;
  }

  calculateCost(from: string, to: string): number {
    const distance = this.getDistance(from, to);
    return distance * this.rate;
  }

  getDistance(from: string, to: string): number {
    // Mock distance calculation
    return  Math.floor(Math.random() * 201);
  }
}

Enter fullscreen mode Exit fullscreen mode

Bike Service Component

import { Injectable } from '@angular/core';
import { TransportationService } from './transportation.service';

@Injectable({ providedIn: 'root' })
export class BikeService implements TransportationService {
  rates =10;
  book(from: string, to: string): string {
    const cost = this.calculateCost(from, to);
    return `Bike booked successfully! From: ${from}, To: ${to}, Cost: $${cost}`;
  }

  calculateCost(from: string, to: string): number {
    const distance = this.getDistance(from, to);
    return distance * this.rate;
  }

  getDistance(from: string, to: string): number {
    // Mock distance calculation
    return  Math.floor(Math.random() * 201);
  }
}
Enter fullscreen mode Exit fullscreen mode

Bus Service Component

import { Injectable } from '@angular/core';
import { TransportationService } from './transportation.service';

@Injectable({ providedIn: 'root' })
export class BusService implements TransportationService {
  rates =10;
  book(from: string, to: string): string {
    const cost = this.calculateCost(from, to);
    return `Bus booked successfully! From: ${from}, To: ${to}, Cost: $${cost}`;
  }

  calculateCost(from: string, to: string): number {
    const distance = this.getDistance(from, to);
    return distance * this.rate;
  }

  getDistance(from: string, to: string): number {
    // Mock distance calculation
    return  Math.floor(Math.random() * 201);
  }
}
Enter fullscreen mode Exit fullscreen mode

Train Service Component

import { Injectable } from '@angular/core';
import { TransportationService } from './transportation.service';

@Injectable({ providedIn: 'root' })
export class TrainService implements TransportationService {
  rates =10;
  book(from: string, to: string): string {
    const cost = this.calculateCost(from, to);
    return `Train booked successfully! From: ${from}, To: ${to}, Cost: $${cost}`;
  }

  calculateCost(from: string, to: string): number {
    const distance = this.getDistance(from, to);
    return distance * this.rate;
  }

  getDistance(from: string, to: string): number {
    // Mock distance calculation
    return  Math.floor(Math.random() * 201);
  }
}

Enter fullscreen mode Exit fullscreen mode

3. Create a Generic Factory Service

The factory service dynamically resolves the transportation service based on the type, with added support for generics.

import { Injectable, inject, Injector } from '@angular/core';
import { TransportationService } from './transportation.service';
import { CarService } from './car.service';
import { BikeService } from './bike.service';
import { BusService } from './bus.service';
import { TrainService } from './train.service';

@Injectable({ providedIn: 'root' })
export class TransportationFactory {
  private injector = inject(Injector);

  public getTransportationService(mode: string): TransportationService {
    const serviceMapping: Record<string, { new (...args: any[]): TransportationService }> = {
      car: CarService,
      bike: BikeService,
      bus: BusService,
      train: TrainService,
    };

    const serviceClass = serviceMapping[mode];
    if (!serviceClass) {
      throw new Error(`Invalid transportation mode: ${mode}`);
    }

    return this.getInstance(serviceClass);
  }

  private getInstance<T extends TransportationService>(serviceClass: { new (...args: any[]): T }): T {
    return this.injector.get(serviceClass);
  }
}

Enter fullscreen mode Exit fullscreen mode

4. Integrate the Factory in a Component

Let's create a component where the user selects the transportation mode, and the appropriate service is booked dynamically.

Transportation Booking Component

import { Component, inject } from '@angular/core';
import { TransportationFactory } from './transportation-factory.service';
import { FormsModule } from '@angular/forms';

@Component({
  standalone: true,
  selector: 'app-transportation-booking',
  imports: [FormsModule],
  template: `
    <div class="container">
      <h1>Book Your Transportation</h1>
      <div>
        <label for="from">From:</label>
        <input id="from" [(ngModel)]="from" placeholder="Enter starting location" />
      </div>
      <div>
        <label for="to">To:</label>
        <input id="to" [(ngModel)]="to" placeholder="Enter destination" />
      </div>
      <div>
        <label for="mode">Mode:</label>
        <select id="mode" [(ngModel)]="selectedMode">
          <option value="car">Car</option>
          <option value="bike">Bike</option>
          <option value="bus">Bus</option>
          <option value="train">Train</option>
        </select>
      </div>
      <button (click)="bookTransportation()">Book</button>
      <p>{{ bookingMessage }}</p>
    </div>
  `,
  styles: [
    `
      .container {
        text-align: center;
        margin: 20px;
      }
      input,
      select {
        margin: 10px;
        padding: 5px;
      }
      button {
        margin-top: 15px;
      }
    `,
  ],
  providers: [
    CarService,
    BikeService,
    BusService,
    TrainService,
    TransportationFactory,
  ],
})
export class TransportationBookingComponent {
  from: string = '';
  to: string = '';
  selectedMode: string = 'car';
  bookingMessage: string = '';

  private transportationFactory = inject(TransportationFactory);

  bookTransportation(): void {
    if (!this.from || !this.to) {
      this.bookingMessage = 'Please enter both From and To locations';
      return;
    }

    const service = this.transportationFactory.getTransportationService(this.selectedMode);
    this.bookingMessage = service.book(this.from, this.to);
  }
}
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Extensibility: Adding new transportation modes is easy; just create a new service and register it with the factory.

  • Maintainability: Changes to a specific mode's logic are isolated.

  • Testability: Easier to mock and test individual services and the factory.

  • Decoupling: The booking component doesn't depend on concrete service implementations.

Conclusion

The Factory Design Pattern is a powerful way to create objects dynamically in Angular applications. By implementing it in our transportation service booking example, we've demonstrated how this pattern promotes flexibility, maintainability, and scalability.
This implementation also highlights the use of Angular 18 features, such as standalone components, making it a modern and efficient solution. With a structured approach and proper separation of concerns, this design pattern can be applied to a wide range of use cases.

Billboard image

Use Playwright to test. Use Playwright to monitor.

Join Vercel, CrowdStrike, and thousands of other teams that run end-to-end monitors on Checkly's programmable monitoring platform.

Get started now!

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

AWS GenAI LIVE!

GenAI LIVE! is a dynamic live-streamed show exploring how AWS and our partners are helping organizations unlock real value with generative AI.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️