How to increase cohesion in your Angular Application

  • by
Photo by Jonathan Borba on Unsplash
Photo by Jonathan Borba on Unsplash

When I hear Angular I think modularity. The component-based structure of angular allows for reusability, easy maintenance, improves readability and simplified unit testing. The framework offers a cohesive manner to progress your application and is a highly popular open-source web application framework.

In an angular component class, you have to ability to inject services. Services contain methods used to maintain data throughout the lifetime of your application. These services expose methods to components that are then used to build up a comment’s definition. Architecting these services can be very difficult, I often find a trail of technical debt in these components and services. Over time these services get larger with methods and the components’ complexity increases with conditional statements, passing one service result to another, these control flows are often copied to other components. When an angular application reaches this point, the advantages of the framework are lost as a result of low cohesion within the services and components.

How can we maintain the high level of cohesion angular has to offer with its component-based structure?

For me, it starts with the Single Responsibility Principle. When I bring up this topic amongst my peers everyone seems to agree a class should have a single responsibility, however, when I break a class with ten methods into ten classes I often get resistance. At this point, there is no real difference between either, besides having nine extra classes, you will only get value from this principle if you utilize the benefits it has to offer in conjunction with other principles and patterns.

According to Wikipedia cohesion is used to indicate the degree to which a class has a single, well-focused purpose. Coupling is all about how classes interact with each other, on the other hand, cohesion focuses on how a single class is designed. The higher the cohesiveness of the class, the better the OO design.

Benefits of higher cohesion allow for easier maintenance and less frequent changes, such classes are more usable than others as they are designed with a well-focused purpose. In my opinion, a class that has additional methods outside of a shared contract is at risk of losing its a well-known purpose. Frameworks like angular control components through abstraction to achieve high cohesion, we can easily achieve the same standard by applying a few patterns and principles when architecting a service layer

In the first sprint of our angular application development, we develop a user story to save a user. This feature states that we have to validate the name, cell number and email address of the request before allowing it to persist. Consider the classes below each with a single responsibility. We have classes accepting interfaces for classes sharing property definitions, classes that handle validation, business logic, and data persistence, each class with a single well-known purpose.

@Injectable({ providedIn: ‘root’ })

export class SaveUserValid extends Handler {

handle(req: SaveUserRequest, rsp: SaveUserResponse) {

if (!req.name)

rsp.errors.push(“Name can not be empty”);

}

}

@Injectable({ providedIn: ‘root’ })

export class CellNumberValid extends Handler {

handle(req: ICellNumberProperty, rsp: HandlerResponse) {

if (!req.cellNumber)

rsp.errors.push(“Cell Number can not be empty”);

}

}

@Injectable({ providedIn: ‘root’ })

export class EmailValid extends Handler {

handle(req: IEmailProperty, rsp: HandlerResponse): any {

if (!req.email)

rsp.errors.push(“Cell Number can not be empty”);

}

}

@Injectable({ providedIn: ‘root’ })

export class SaveUserWorker extends Handler {

handle(req: SaveUserRequest, rsp: SaveUserResponse) {

//Apply business logic and persist to data store

rsp.successTest = true;

}

}


At this point, it seems like a lot of effort to use these handlers in a component. Let’s see how we can use the abstract handler contract to our advantage.

Instead of using the classic constructor injection, let’s create a dynamic class that will accept the request and response parameters of the abstract handler method as well as a list of handlers we would like to execute and let it do the execution work for us. Because the handlers conform to the same contract this class is not going to be concerned with the specific details of each handler. Let’s call this class the Execution Pipeline and inject it into our component constructor. The component now looks as follows.

@Component({

selector: ‘app-root’,

templateUrl: ‘./app.component.html’,

styleUrls: [‘./app.component.less’]

})

export class AppComponent {

title = ‘PipesAndFilters’;

constructor(private pipeLine: ExecutionPipeline) {

}

public Save(): void {

var req = new SaveUserRequest();

var rsp = new SaveUserResponse();

rsp.successTest = false;

this.pipeLine.Execute(req, rsp,

[

‘CellNumberValid’,

‘EmailValid’,

‘SaveUserValid’,

‘SaveUserWorker’,

]);

alert(rsp.errors.length);

alert(rsp.successTest);

}

}


We have now inverted the way traditional services works. Four sprints later we receive a more complicated user story listing requirements to update a user and send a notification according to the user’s communication preferences if the email address has changed. To achieve this you create the following set of classes.

@Injectable({providedIn: ‘root’})

export class UpdateUserWorker extends Handler {

handle(req: IUserReq, rsp: IUpdateUserRsp) {

//Apply business logic and persist to data store

// if email changed

rsp.emailChange = true;

rsp.successTest = true;

}

}

@Injectable({providedIn: ‘root’})

export class UpdatedEmailAddressNotification extends Handler {

handle(req: any, rsp: IUpdateEmailNotificationRsp) {

if (!rsp.emailChange)

return;

rsp.message = “your emails has been updated”;

this.pipeline.Execute(req, rsp,

[

“SmsNotification”,

“EmailNotification”

]);

}

}

@Injectable({providedIn: ‘root’})

export class SmsNotification extends Handler {

constructor(private userComPref: UserCommunicationPreferences) {

super();

}

handle(req: any, rsp: INotificationMessage) {

if (this.userComPref.sms)

alert(rsp.message);

}

}

@Injectable({providedIn: ‘root’})

export class EmailNotification extends Handler {

constructor(private userComPref: UserCommunicationPreferences) {

super();

}

handle(req: any, rsp: INotificationMessage) {

if (this.userComPref.email)

alert(rsp.message);

}

}


When we use these in our component we can reuse some of the previous validations and extend the pipeline with new handlers to meet the requirements.

const req = {

name: ‘test’,

email: ‘test@test.com’,

cellNumber: ‘1234’,

};

let rsp = new SaveUserResponse();

rsp.successTest = false;

this.pipeLine.Execute(req, rsp,

[

CellNumberValid,

‘EmailValid’,

‘UpdateUserValid’,

‘UpdateUserWorker’,

‘UpdatedEmailAddressNotification’,

]);


Handlers can now be extracted from the project and reused within other projects, they can also be conceptually categorized in folders similar to the angular component file structure. Managing complexity regarding the services has been reduced and abstracted, leaving the components cleaner and easier to read. The principles and patterns applied in this concept have proved advantageous in many of my solutions in the past. If you would like to read up on then follow the link to my showcase and if you are interested in the source code for this article follow the link to GitHub