Angular: Clean Code with Strategy Pattern

SadmanJahin
3 min readOct 7, 2023

Angular is a powerful JavaScript framework for building dynamic web applications. Angular provides wide ranges of libraries, flexibility, strong rule to follow the types and architectures which made it best.

The Strategy Pattern is a design pattern used in software development to define a family of interchangeable algorithms, encapsulate each one, and make them interchangeable. In the context of Angular, the Strategy Pattern can be a game-changer, allowing you to dynamically select and switch between different algorithms or behaviors at runtime. This flexibility is especially useful when dealing with complex decision-making processes or implementing various strategies in your application.

If we deep down to the example,

<div>
<button (click)="exportData('pdf')">Export as PDF</button>
<button (click)="exportData('doc')">Export as Doc</button>
<button (click)="exportData('xml')">Export as XML</button>
</div>

export class AppComponent {
exportData(format: string): void {
switch (format) {
case 'pdf':
console.log("Export as pdf");
break;
case 'doc':
console.log("Export as doc");
break;
case 'xml':
console.log("Export as xml");
break;
default:
break;
}
}

Here, the export data function has conditional export options. If the format increases, this function size may increase as well as the export condition, decreases readability of the code.
So, we can break them into separate functions.

<div>
<button (click)="exportDataInPDF()">Export as PDF</button>
<button (click)="exportDataInDOC()">Export as DOC</button>
<button (click)="exportDataInXML()">Export as XML</button>
</div>

export class AppComponent {
exportDataInPDF() {
console.log("Exporting in PDF")
}
exportDataInDOC() {
console.log("Exporting in DOC")
}
exportDataInXML() {
console.log("Exporting in XML")
}
}

But Exporting Functionality/Algorithm can be common throughout some modules. We can use strategy pattern to get help making common scenarios.

Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.

Strategy Pattern can
1.Prevents the conditional statements. (switch, if, else…)

2.The algorithms are loosely coupled with the context . They can be changed/replaced without changing the context.

3.Very easy extendable.

But cons are
1. Increases overhead.
2. Need to know about the strategies.
3.Increase no of objects.

export interface IExportStrategy {
export();
}
export class PDFExportStrategy implements IExportStrategy {
public export() {
console.log("Exporting as PDF");
}
}
export class DOCExportStategy implements IExportStrategy {
public export() {
console.log("Exporting as Doc");
}
}
export class XMLExportStategy implements IExportStrategy {
public export() {
console.log("Exporting as XML");
}
}
<div>
<button (click)="exportDataInPDF()">Export as PDF</button>
<button (click)="exportDataInDOC()">Export as DOC</button>
<button (click)="exportDataInXML()">Export as XML</button>
</div>

export class AppComponent {
exportDataInPDF() {
let exportStrategy:IExportStrategy = new PDFExportStrategy();
exportStrategy.export();
}
exportDataInDOC() {
let exportStrategy:IExportStrategy = new DOCExportStategy();
exportStrategy.export();
}
exportDataInXML() {
let exportStrategy:IExportStrategy = new XMLExportStategy();
exportStrategy.export();
}
}

But File export may need a lot of functionality before it gets exported (like compression etc). We create a file service then inject it to our app component.

export class FileService {
constructor() {
}
processFile(){
console.log("Proecssing File");
}
exportFile<T extends IExportStrategy>(t: T){
t.export();
}
}
export class AppComponent {
constructor(private fileService: FileService) { }

exportDataInPDF() {
this.fileService.processFile();
this.fileService.exportFile(new PDFExportStrategy());
}

exportDataInDOC() {
this.fileService.processFile();
this.fileService.exportFile(new DOCExportStrategy());
}

exportDataInXML() {
this.fileService.processFile();
this.fileService.exportFile(new XMLExportStategy());
}
}

Optionally, we can use factory method to create instances of strategies.

export class StrategyFactory {
static createStrategy<T extends IExportStrategy>(c: new () => T): T {
return new c();
}
}
export class AppComponent {
constructor(private fileService: FileService) { }
exportDataInPDF() {
this.fileService.processFile();
this.fileService.exportFile(StrategyFactory.createStrategy(PDFExportStrategy));
}
exportDataInDOC() {
this.fileService.processFile();
this.fileService.exportFile(StrategyFactory.createStrategy(DOCExportStrategy));
}
exportDataInXML() {
this.fileService.processFile();
this.fileService.exportFile(StrategyFactory.createStrategy(XMLExportStategy));
}

We can achieve more clean structure by using it through removal of
1. Code duplication (Any component and services can use the export logics).
2. Code flexibility & extensibility (More strategies can be added by implementing the interface rather than using more switch case).
3. Easily testable & readable.
4. Separation of algorithm from client code.
5.UI Level Single Responsibility (a button tagged with a click event function only serves one purpose)

--

--