Design Patterns in Angular: Strategy Design Pattern

Sagarnath S
3 min readNov 30, 2024

--

Strategy Design Pattern

The Strategy Design Pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. The key idea is that the client can choose which algorithm (or strategy) to use at runtime. This is particularly useful in situations where you need to change the behavior of an object dynamically based on some condition.

In Angular, implementing the Strategy Pattern can help adhere to the SOLID principles, especially the Open/Closed Principle (open for extension, closed for modification) and the Dependency Inversion Principle.

Steps to Implement Strategy Design Pattern in Angular

  • Define the Strategy Interface: Define a common interface for the strategies that will be interchangeable.
  • Create Concrete Strategies: Implement concrete strategies that perform different behaviors.
  • Context Class: A class that uses a reference to a strategy and delegates work to the strategy.
  • Dependency Injection: Leverage Angular’s Dependency Injection (DI) to inject different strategies into a component or service.

Example: Payment System with Strategy Pattern

Create a Payment system where a user can select a payment strategy (Credit Card, PayPal, Cash). The payment system will use the Strategy Pattern to handle the different payment methods without modifying the core payment logic

Step 1: Define the Strategy Interface

export interface PaymentStrategy {
pay(amount: number): string;
}

Step 2: Implement Concrete Strategies

import { PaymentStrategy } from './payment-strategy.interface';

export class CreditCardPayment implements PaymentStrategy {
pay(amount: number): string {
return `Paid ${amount} using Credit Card.`;
}
}
import { PaymentStrategy } from './payment-strategy.interface';

export class PayPalPayment implements PaymentStrategy {
pay(amount: number): string {
return `Paid ${amount} using PayPal.`;
}
}
import { PaymentStrategy } from './payment-strategy.interface';

export class CashPayment implements PaymentStrategy {
pay(amount: number): string {
return `Paid ${amount} using Cash.`;
}
}

Step 3: Create the Context Class
This class will use the strategy and delegate the payment operation

import { Injectable } from '@angular/core';
import { PaymentStrategy } from './payment-strategy.interface';
import { CreditCardPayment } from './credit-card-payment.strategy';

@Injectable({
providedIn: 'root'
})
export class PaymentContextService {

private paymentStrategy: PaymentStrategy;

constructor() {
//Default value 😊
this.paymentStrategy = new CreditCardPayment();
}

setPaymentStrategy(strategy: PaymentStrategy) {
this.paymentStrategy = strategy;
}

executePayment(amount: number): string {
return this.paymentStrategy.pay(amount);
}
}

Step 4: Use Dependency Injection in Angular Component
Now, use the PaymentContextService in an Angular component and dynamically switch payment strategies.

import { Component } from '@angular/core';
import { PaymentContextService } from './services/payment-context.service';
import { CreditCardPayment } from './services/credit-card-payment.strategy';
import { PayPalPayment } from './services/paypal-payment.strategy';
import { CashPayment } from './services/bitcoin-payment.strategy';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {

constructor(private paymentContext: PaymentContextService) {}

makePayment(amount: number, paymentMethod: string) {
switch (paymentMethod) {
case 'credit':
this.paymentContext.setPaymentStrategy(new CreditCardPayment());
break;
case 'paypal':
this.paymentContext.setPaymentStrategy(new PayPalPayment());
break;
case 'bitcoin':
this.paymentContext.setPaymentStrategy(new CashPayment());
break;
default:
console.log('Invalid payment method');
return;
}

const result = this.paymentContext.executePayment(amount);
console.log(result);
}
}

Explanation

  1. Strategy Interface (PaymentStrategy): This is the contract that all payment strategies must adhere to. Each concrete strategy (Credit Card, PayPal, Cash) will implement the pay method defined in the interface.
  2. Concrete Strategies (CreditCardPayment, PayPalPayment, CashPayment): Each of these classes implements the PaymentStrategy interface and provides its own logic for handling payments.
  3. PaymentContextService: This service is the “context” that holds a reference to the current strategy. It allows the payment strategy to be swapped at runtime through the setPaymentStrategy() method. The executePayment() method delegates the payment operation to the selected strategy.
  4. Component (AppComponent): The Angular component uses the PaymentContextService to execute payments. It can switch between different strategies dynamically by calling setPaymentStrategy() based on user input (e.g., button clicks).

Benefits of this Implementation:

  1. Adheres to SOLID Principles:
  • Single Responsibility Principle: Each class has a single responsibility. For example, CreditCardPayment handles credit card payments, PayPalPayment handles PayPal payments, etc.
  • Open/Closed Principle: The payment system is open for extension but closed for modification. You can add new payment methods without modifying existing code by simply creating a new strategy.
  • Dependency Inversion Principle: High-level modules (e.g., PaymentContextService) depend on the abstraction (PaymentStrategy), not on low-level implementations like CreditCardPayment, PayPalPayment, etc.
  1. Flexibility: The payment strategy can be easily swapped based on user input, enabling flexible and maintainable code.

Thank you for reading if you have any doubts. drop the message in comments.

Follow me on Medium or LinkedIn to read more about Angular and TS!

--

--

Sagarnath S
Sagarnath S

Written by Sagarnath S

Software Development Engineer - I @CareStack || Web Developer || Angular || ASP.Net Core || C# || TypeScript || HTML || CSS

No responses yet