Design Patterns in Angular: Command Method Design Pattern

Sagarnath S
5 min readDec 7, 2024

--

Command Method Design Pattern in Angular

The Command Design Pattern is a behavioral design pattern that turns a request into a stand-alone object called a command. With the help of this pattern, you can capture each component of a request, including the object that owns the method, the parameters for the method, and the method itself

In Angular, this pattern can be implemented to encapsulate user actions or commands, making the application easier to extend, test, and maintain.

The key components of the Command Design Pattern are:

Command: This is an interface or abstract class that declares the method execute(). Concrete command classes implement this interface and define the specific actions to be taken when the command is executed.

ConcreteCommand: A subclass of Command, this class defines the binding between a receiver object and an action. It implements the execute() method to invoke the appropriate operation(s) on the receiver.

Client: The client creates a ConcreteCommand object and sets the receiver for the command. It then passes the command to the invoker.

Invoker: The invoker is responsible for initiating the request (or command). It stores the command and can call the execute() method at a later time, either on demand or in response to some event or action.

Receiver: The receiver is the object that knows how to perform the action to satisfy the request. It holds the business logic that needs to be executed when the command is triggered.

Invoker’s Request History (Optional): Sometimes the invoker might keep track of the commands in a history (queue or stack) for purposes like undo/redo or retry.

Let’s implement a Text Editor example where the user can perform actions like bold, italic, and underline. We’ll follow the SOLID principles to make sure the design is clean, scalable, and maintainable.

Project Structure

src/
├── app/
│ ├── app.component.ts
│ ├── app.component.html
│ ├── services/
│ │ ├── text-editor.service.ts
│ │ ├── command-invoker.service.ts
│ ├── commands/
│ │ ├── command.interface.ts
│ │ ├── bold-command.ts
│ │ ├── italic-command.ts
│ │ ├── underline-command.ts
│ ├── models/
│ │ ├── text-state.ts
│ ├── app.module.ts

1. Text State Model (SRP)

This model will hold the current state of the text (e.g., with added styles).

export interface TextState {
content: string;
isBold: boolean;
isItalic: boolean;
isUnderlined: boolean;
}

2. Text Editor Service (SRP)

This service manages the current text content and its styles.

import { Injectable } from '@angular/core';
import { TextState } from '../models/text-state';

@Injectable({
providedIn: 'root',
})
export class TextEditorService {
private state: TextState = { content: '', isBold: false, isItalic: false, isUnderlined: false };

getState(): TextState {
return { ...this.state };
}

setState(state: TextState): void {
this.state = state;
}

applyBold(): void {
this.state.isBold = !this.state.isBold;
}

applyItalic(): void {
this.state.isItalic = !this.state.isItalic;
}

applyUnderline(): void {
this.state.isUnderlined = !this.state.isUnderlined;
}

updateContent(content: string): void {
this.state.content = content;
}
}

3. Command Interface (ISP)

The command interface defines the execute() and undo() methods that all commands will implement.

export interface Command {
execute(): void;
undo(): void;
}

4. Concrete Command Implementations

Each formatting action (bold, italic, underline) will be encapsulated as a separate command.

Bold Command

import { Command } from './command.interface';
import { TextEditorService } from '../services/text-editor.service';

export class BoldCommand implements Command {
private previousState: boolean;

constructor(private textEditor: TextEditorService) {}

execute(): void {
this.previousState = this.textEditor.getState().isBold;
this.textEditor.applyBold();
}

undo(): void {
if (this.previousState !== undefined) {
this.textEditor.setState({
...this.textEditor.getState(),
isBold: this.previousState,
});
}
}
}

Italic Command

import { Command } from './command.interface';
import { TextEditorService } from '../services/text-editor.service';

export class ItalicCommand implements Command {
private previousState: boolean;

constructor(private textEditor: TextEditorService) {}

execute(): void {
this.previousState = this.textEditor.getState().isItalic;
this.textEditor.applyItalic();
}

undo(): void {
if (this.previousState !== undefined) {
this.textEditor.setState({
...this.textEditor.getState(),
isItalic: this.previousState,
});
}
}
}

Underline Command

import { Command } from './command.interface';
import { TextEditorService } from '../services/text-editor.service';

export class UnderlineCommand implements Command {
private previousState: boolean;

constructor(private textEditor: TextEditorService) {}

execute(): void {
this.previousState = this.textEditor.getState().isUnderlined;
this.textEditor.applyUnderline();
}

undo(): void {
if (this.previousState !== undefined) {
this.textEditor.setState({
...this.textEditor.getState(),
isUnderlined: this.previousState,
});
}
}
}

5. Command Invoker (DIP)

The invoker is responsible for storing and invoking commands. It also handles undo/redo functionality.

import { Injectable } from '@angular/core';
import { Command } from '../commands/command.interface';

@Injectable({
providedIn: 'root',
})
export class CommandInvokerService {
private history: Command[] = [];
private redoStack: Command[] = [];

execute(command: Command): void {
command.execute();
this.history.push(command);
this.redoStack = [];
}

undo(): void {
const command = this.history.pop();
if (command) {
command.undo();
this.redoStack.push(command);
}
}

redo(): void {
const command = this.redoStack.pop();
if (command) {
command.execute();
this.history.push(command);
}
}
}

6. Component (SRP)

The component allows the user to interact with the text editor and perform actions like bold, italic, or underline

import { Component } from '@angular/core';
import { CommandInvokerService } from './services/command-invoker.service';
import { TextEditorService } from './services/text-editor.service';
import { BoldCommand } from './commands/bold-command';
import { ItalicCommand } from './commands/italic-command';
import { UnderlineCommand } from './commands/underline-command';

@Component({
selector: 'app-root',
template: `
<textarea [(ngModel)]="textContent" (ngModelChange)="onContentChange()"></textarea>
<br />
<button (click)="toggleBold()">Bold</button>
<button (click)="toggleItalic()">Italic</button>
<button (click)="toggleUnderline()">Underline</button>
<br />
<button (click)="undo()">Undo</button>
<button (click)="redo()">Redo</button>
`,
})
export class AppComponent {
textContent = '';

constructor(
private textEditor: TextEditorService,
private commandInvoker: CommandInvokerService
) {}

onContentChange(): void {
this.textEditor.updateContent(this.textContent);
}

toggleBold(): void {
const command = new BoldCommand(this.textEditor);
this.commandInvoker.execute(command);
}

toggleItalic(): void {
const command = new ItalicCommand(this.textEditor);
this.commandInvoker.execute(command);
}

toggleUnderline(): void {
const command = new UnderlineCommand(this.textEditor);
this.commandInvoker.execute(command);
}

undo(): void {
this.commandInvoker.undo();
}

redo(): void {
this.commandInvoker.redo();
}
}

Explanation of the Code

TextEditorService: Manages the text content and its formatting states (bold, italic, underline). It can apply and undo each of these styles.

Command Pattern: Each action (BoldCommand, ItalicCommand, UnderlineCommand) is a separate command that modifies the text editor’s state.

CommandInvokerService: Responsible for executing commands and keeping track of the history for undo and redo operations.

Component: Provides an interface for the user to interact with the text editor, and uses the CommandInvokerService to execute commands.

When to use the Command Design Pattern

Decoupling is Needed:

  • In order to separate the requester making the request (the sender) from the object executing it, use the Command Pattern.
  • Your code will become more expandable and adaptable as a result.

Undo/Redo Functionality is Required:

  • If you need to support undo and redo operations in your application, the Command Pattern is a good fit.
  • Each command can encapsulate an operation and its inverse, making it easy to undo or redo actions.

Support for Queues and Logging:

  • If you want to maintain a history of commands, log them, or even put them in a queue for execution, the Command Pattern provides a structured way to achieve this.

Dynamic Configuration:

  • When you need the ability to dynamically configure and assemble commands at runtime, the Command Pattern allows for flexible composition of commands.

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

Responses (1)