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.