Strategy Pattern Implementation with Typescript and Angular

The Strategy Pattern is a design pattern that allows you to define a family of algorithms, encapsulate each one of them, and make them interchangeable. In Angular, we can implement this pattern using Dependency Injection (DI) and abstract classes. In this blog post, we’ll explore how to achieve this, creating a Calculator Service that performs various mathematical operations.

The Abstract Strategy Class

The first step is to create an abstract class that defines the strategy interface. This abstract class will serve as the blueprint for our concrete strategy classes.

math-strategy.ts

export abstract class MathStrategy {
  abstract execute(a: number, b: number): number;
}

Creating Concrete Strategy Classes

Next, we create concrete strategy classes that extend the abstract MathStrategy class. Each concrete class provides a specific implementation for a mathematical operation.

add-strategy.ts

import { MathStrategy } from './math-strategy';

export class AddStrategy extends MathStrategy {
  execute(a: number, b: number): number {
    return a + b;
  }
}

subtract-strategy.ts

import { MathStrategy } from './math-strategy';

export class SubtractStrategy extends MathStrategy {
  execute(a: number, b: number): number {
    return a - b;
  }
}

multiply-strategy.ts

import { MathStrategy } from './math-strategy';

export class MultiplyStrategy extends MathStrategy {
  execute(a: number, b: number): number {
    return a * b;
  }
}

divide-strategy.ts

import { MathStrategy } from './math-strategy';

export class DivideStrategy extends MathStrategy {
  execute(a: number, b: number): number {
    if (b === 0) {
      throw new Error("Cannot divide by zero");
    }
    return a / b;
  }
}

The Calculator Service

Now, let’s create a CalculatorService that will use these strategies based on user input. We’ll use Dependency Injection to manage the strategies.

import { Injectable, InjectionToken, Inject } from '@angular/core';
import { MathStrategy } from './math-strategy';

export const STRATEGY_TOKEN = new InjectionToken<Map<string, MathStrategy>>('StrategyToken');

@Injectable()
export class CalculatorService {
  constructor(@Inject(STRATEGY_TOKEN) private strategies: Map<string, MathStrategy>) {}

  calculate(a: number, b: number, operation: string): number {
    const strategy = this.strategies.get(operation);

    if (!strategy) {
      throw new Error('Invalid operation');
    }

    return strategy.execute(a, b);
  }
}

In this code, we use Angular’s InjectionToken and @Inject to inject the strategies directly into the CalculatorService.

Providing Dependencies in the Angular Module

To complete the setup, we need to provide the dependencies in our Angular module. We’ll register the concrete strategy classes and the CalculatorService with Angular’s DI system.

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { CalculatorComponent } from './calculator.component';
import { AddStrategy, SubtractStrategy, MultiplyStrategy, DivideStrategy } from './strategies';
import { CalculatorService, STRATEGY_TOKEN } from './calculator.service';
import { MathStrategy } from './math-strategy';

@NgModule({
  imports: [BrowserModule, FormsModule],
  declarations: [AppComponent, CalculatorComponent],
  providers: [
    { provide: STRATEGY_TOKEN, useValue: new Map<string, MathStrategy>([
      ['add', new AddStrategy()],
      ['subtract', new SubtractStrategy()],
      ['multiply', new MultiplyStrategy()],
      ['divide', new DivideStrategy()],
    ]) },
    CalculatorService,
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Using the Calculator Component

Finally, you can use the CalculatorComponent in your application by adding it to your template, as shown below:

calculator.component.ts

// calculator.component.ts
import { Component } from '@angular/core';
import { CalculatorService } from '../service/calculator.service';

@Component({
  selector: 'app-calculator',
  template: `
    <div>
      <input [(ngModel)]="num1" type="number" />
      <select [(ngModel)]="selectedOperation">
        <option value="add">Add</option>
        <option value="subtract">Subtract</option>
        <option value="multiply">Multiply</option>
        <option value="divide">Divide</option>
      </select>
      <input [(ngModel)]="num2" type="number" />
      <button (click)="calculate()">Calculate</button>
      <p>Result: {{ result }}</p>
    </div>
  `,
})
export class CalculatorComponent {
  num1: number = 0;
  num2: number = 0;
  selectedOperation: string = 'add';
  result: number = 0;

  constructor(private calculatorService: CalculatorService) {}

  calculate() {
    this.result = this.calculatorService.calculate(
      this.num1,
      this.num2,
      this.selectedOperation
    );
  }
}

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>Strategy Pattern with Dependency Injection and Abstract Classes in Angular</h1>
    <app-calculator></app-calculator>
  `,
})
export class AppComponent {}

With this implementation, you’ve successfully applied the Strategy Pattern in Angular using Dependency Injection and abstract classes. Your code is modular, maintainable, and extensible, allowing you to easily switch or add new strategies without modifying existing code.

Demo