Design Patterns — Chain of Responsibility in Angular

Image generated by Copilot AI

Behavioral patterns are design patterns that deal with the ways objects communicate with each other.

Behavioral patterns consist of:

1. Chain of Responsibility
2. Command
3. Interpreter
4. Iterator
5. Mediator
6. Memento
7. Observer
8. State
9. Strategy
10. Template Method

In this article, I will explain how the Chain of Responsibility pattern works. You can see and run an example of the Chain of Responsibility pattern in a demo project, which can be found at this link here.

The Chain of Responsibility pattern allows a certain number of classes to attempt to handle a request, while none of them know about the capabilities of the other classes. This makes it possible to eliminate direct dependencies between these classes. The only common link between them is the request being passed along. The request is passed until a class capable of handling it is found. Classes that handle the request or pass it to the next class are called handlers. The pattern suggests that you link these handlers into a chain. Each linked handler has a field for storing a reference to the next handler in the chain.

In our example, each class has a method that decides whether to pass the entered form data. The passing is done using: super.handle(request);

The example of the Chain of Responsibility design pattern includes a form that verifies whether the data is entered. If any field is not filled in, the data is not passed further. Below are the classes that decide whether to process the request (data) in the subsequent steps:

The service class PersonalInfoStepHandlerService contains the method isValidPersonalInfo that verifies if the first name and surname fields are filled in, and if so, the request is passed further:

import { Injectable } from '@angular/core';
import { AbstractStepHandler } from '../../../models/step-handler';

@Injectable({
providedIn: 'root'
})
export class PersonalInfoStepHandlerService extends AbstractStepHandler {

public override handle(request: any): void {
if (this.isValidPersonalInfo(request)) {
console.log('Step 1: Personal data correct.');
super.handle(request);
} else {
console.log('Step 1: Incorrect personal data.');
}
}

private isValidPersonalInfo(request: any): boolean {
return request.name && request.surname;
}
}

The service class AddressStepHandlerService contains the method isValidAddress that verifies if the address and city fields are filled in, and if so, the request is passed further:

import { Injectable } from '@angular/core';
import { AbstractStepHandler } from '../../../models/step-handler';

@Injectable({
providedIn: 'root'
})
export class AddressStepHandlerService extends AbstractStepHandler {

public override handle(request: any): void {
if (this.isValidAddress(request)) {
console.log('Step 2: Address details are correct.');
super.handle(request);
} else {
console.log('Step 2: Incorrect address details.');
}
}

private isValidAddress(request: any): boolean {
return request.address && request.city;
}
}

The service class ConfirmationStepHandlerService does not pass the request further because it displays a message confirming that all previously verified fields have been filled out:

import { Injectable } from '@angular/core';
import { AbstractStepHandler } from '../../../models/step-handler';

@Injectable({
providedIn: 'root'
})
export class ConfirmationStepHandlerService extends AbstractStepHandler {

public override handle(request: any): void {
console.log('Step 3: Confirmation of registration.');
console.log('Registration completed successfully.');
}
}

Each of these classes, PersonalInfoStepHandlerService, AddressStepHandlerService, and ConfirmationStepHandlerService, inherits from the abstract class AbstractStepHandler, which defines a method to verify whether the request should be passed on and a method to forward the request:

export abstract class AbstractStepHandler implements StepHandler {
private nextHandler: StepHandler | null = null;

public setNext(handler: StepHandler): StepHandler {
this.nextHandler = handler;
return handler;
}

public handle(request: any): void {
if (this.nextHandler) {
this.nextHandler.handle(request);
}
}
}

The abstract class implements the AbstractStepHandler interface:

export interface StepHandler {
setNext(handler: StepHandler): StepHandler;
handle(request: any): void;
}

The implementation of the ChainOfResponsibilityComponent, where the user can enter data and trigger the Chain of Responsibility design pattern, is shown below:

import { Component } from '@angular/core';
import { RegistrationService } from '../services/registration.service';
import { FormsModule, NgModel, NgModelGroup } from '@angular/forms';

@Component({
selector: 'app-chain-of-responsibility',
standalone: true,
imports: [FormsModule],
template: `
<form (ngSubmit)="onSubmit()">

<div>
<label for="name">Name:</label>
<input type="text" id="name"
[(ngModel)]="userData.name" name="name" required>
</div>

<div>
<label for="surname">Surname:</label>
<input type="text" id="surname"
[(ngModel)]="userData.surname" name="surname" required>
</div>

<div>
<label for="address">Address:</label>
<input type="text" id="address"
[(ngModel)]="userData.address" name="address" required>
</div>

<div>
<label for="city">City:</label>
<input type="text" id="city"
[(ngModel)]="userData.city" name="city" required>
</div>

<button type="submit">Submit</button>
</form>
`,
styleUrl: './chain-of-responsibility.component.scss'
})
export class ChainOfResponsibilityComponent {
userData = {
name: '',
surname: '',
address: '',
city: '',
};
constructor(private registrationService: RegistrationService) {}

onSubmit() {
this.registrationService.startRegistration(this.userData);
}
}

After entering the data in the form, the user clicks the Submit button, which triggers the startRegistration method from the RegistrationService. The initiation of the data transfer chain can be done in two ways. The first way is to inject the PersonalInfoStepHandlerService, AddressStepHandlerService, and ConfirmationStepHandlerService services into the constructor. Then, we need to define the chain of connections using the setNext method from the personalInfoStepHandler service, which creates a connection using the field private nextHandler: StepHandler | null = null;. Next, we call the handle method this.personalInfoStepHandler.handle(request);, which begins the verification of whether the connection to the next handler will be passed.

The second way is to create instances of the PersonalInfoStepHandlerService, AddressStepHandlerService, and ConfirmationStepHandlerService services. The created instances are passed using the setNext method to establish a connection between the classes, which is achieved through the field private nextHandler: StepHandler | null = null;, to which a reference is assigned. Then, the verification process is initiated to determine whether the connection should be passed to the next classes. We do this in the class that is the first step using the handle method.

Below is the service class defining the above two ways of starting the Chain of Responsibility design pattern:

import { Injectable } from '@angular/core';
import { PersonalInfoStepHandlerService } from './step-handler/personal/personal-info-step-handler.service';
import { ConfirmationStepHandlerService } from './step-handler/confirmation/confirmation-step-handler.service';
import { AddressStepHandlerService } from './step-handler/address/address-step-handler.service';

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

constructor(
private personalInfoStepHandler: PersonalInfoStepHandlerService,
private addressStepHandler: AddressStepHandlerService,
private confirmationStepHandler: ConfirmationStepHandlerService
) {}

public startRegistration(request: any): void {

this.personalInfoStepHandler
.setNext(this.addressStepHandler)
.setNext(this.confirmationStepHandler);

this.personalInfoStepHandler.handle(request);

// you can also use the handleRequest method
// this.handleRequest(request);
}

handleRequest(userData: any) {
const handler1 = new PersonalInfoStepHandlerService();
const handler2 = new AddressStepHandlerService();
const handler3 = new ConfirmationStepHandlerService();

const handler4 = handler1.setNext(handler2).setNext(handler3)

handler1.handle(userData);

return handler4
}
}

The structure of the Chain of Responsibility pattern consists of several elements:

  • Client may compose chains just once or compose them dynamically, depending on the application’s logic. Note that a request can be sent to any handler in the chain — it doesn’t have to be the first one. The client, in our case, is the RegistrationService. It calls the handle method on the object that verifies the first fields in the form.
  • Handler declares the interface, common for all concrete handlers. In our case, the StepHandler interface contains the definition of the request handling method and the method for setting up the request passing
  • Base Handler is an class where you can put the boilerplate code that’s common to all handler classes. This class defines a field for storing a reference to the next handler. In our case, these are service classes PersonalInfoStepHandlerService, AddressStepHandlerService, and ConfirmationStepHandlerService.
  • Concrete Handlers contain the actual code for processing requests. Upon receiving a request, each handler must decide whether to process it and, additionally, whether to pass it along the chain. In our case it is the RegistrationService service.

Summary

The main goal of the Chain of Responsibility design pattern is to reduce the coupling between objects. The only thing that individual objects need to know is how to pass a request to other objects. Each object in the chain is independent and does not have information about other objects. It must only decide whether it can handle the received request. It can be determined whether the last object in the chain will handle all requests or if some requests will remain unhandled. However, it is essential to know which object will be the last in the chain.

Materials:

  1. Java. Wzorce projektowe. Translation: Piotr Badarycz. Original title: Java design patterns. A Tutorial. Author: James William Cooper
  2. https://refactoring.guru/pl/design-patterns/chain-of-responsibility

--

--