Photo by Matt Duncan on Unsplash

Understanding Angular — Exploring Dependency Injection and Design Patterns — Part 0 🔥🚀

Giorgio Galassi
6 min readMar 2, 2024

--

This is the 1st of a series of articles that have the goal to explore and understand a topic that I see too much neglected in the front-end world — Dependency Injection (DI) & Design patterns.

You will start writing and understanding the basics of DI — what is it, how does it work and how works in Angular.

Our journey will continue flying between the SOLID principles, followed by some stops here and there to visit different design patterns and the relative use cases. You also may encounter more stuff along the way (this means that i could change my mind and add more things to the roadmap 😎)

Let’s stop thinking about the whole journey — focus on what’s now!

What Dependency Injection (DI) is?

Before getting into it here is a very brief and concise description of what the DI is.

DI is process that encourages the separation of concerns, thereby improving the maintainability and scalability of software systems.

At its core, DI is a technique employed to achieve Inversion of Control (IoC). IoC means that the control over the flow of a program’s execution is inverted, with the framework or container managing the dependencies and their life cycles.

Is not that complex, isn’t it? You and me, use this tool everyday without even noticing it. It’s time to understand the magic behind.

Let’s proceed and see…

Who are the actors in DI?

There are three major actors that are part of this movie called Dependency Injection.

  1. Dependency,
  2. Client,
  3. Injector (optional).

Everyone of them has it’s own importance and the whole flow wouldn’t exist without one of them.

Dependency

A dependency, in this context, refers to an object or service that another object relies upon to perform its function.

Client

The client is the class or component that requires a dependency to fulfill its responsibilities. It is the class that will receive the injected dependency.

Injector

The injector is responsible for providing the necessary dependencies to the client. It acts as an intermediary, resolving and injecting the dependencies into the client class — here is where the IoC takes place.

The Injector is in charge to provide us the correct Service’s instance.

That was a brief introduction of who’s in charge of what, but…

How does DI works without IoC?

You read that the Injector is an optional actor in this film, and it is.

Having someone that can do the work for us does not mean that you can ignore how it works behind the scene.

This chapter will be about use the DI without any help, but our brain.

So, let’s see…

DI in Vanilla TypeScript

Below you can find a little Stackblitz where DI is used, by hand.

By hand means that you are in charge of passing the correct instance of a service to another one that need it to perform some actions.

In the example below we have 2 classes:

  1. Counter,
  2. ElementHandler

Counter

The Counter class uses the ElementHandler class to perform some simple actions on the HTMLElement that represent the Counter that you see on the screen.

Pay attention to the Counter’s class constructor — the argument is a variable of type ElementHandler, the other class that you are going to see.

import { ElementHandler } from './element';

export class Counter {
private _elementHandler: ElementHandler;
private _counter = 0;

constructor(element: ElementHandler) {
this._elementHandler = element;
}

setUpCounter() {
this._elementHandler.attachEventListener('click', () =>
this.setCounter(this._counter + 1)
);

this.setCounter(0);
}

private setCounter(count: number) {
this._counter = count;
this._elementHandler.setInnerHtml(`count is ${this._counter}`);
}
}

ElementHandler

The ElementHandler class is pretty straight forward — it acts as a wrapper for some basic element management (get an Element and add an event to it).

export class ElementHandler {
private _element: HTMLElement | null = null;
private _id: string;

constructor(id: string) {
this._id = id;
this.getElementById(this._id);
}

setInnerHtml(value: string) {
if (this._element) this._element.innerHTML = value;
}

attachEventListener(event: keyof HTMLElementEventMap, cb: any) {
if (this._element) this._element.addEventListener(event, cb);
}

private getElementById(id: string) {
this._element = document.querySelector<HTMLButtonElement>(id)!;
}
}

Putting all together in the…

main.ts

The main file is pretty small, but these are the only lines of code that you need to consider.

As you can see we are manually creating the instances of the services.

Notice that the elementHandler variable is the argument for the Counter class, as you saw when you read about it.

const elementHandler = new ElementHandler('#counter');
const counter = new Counter(elementHandler);

counter.setUpCounter();

How does DI works with IoC?

Well, DI in a nutshell could be explained like this:

Liam “Injector” Neeson from Taken 2

Or… if you want to act as the “Serious” Developer you are, like this:

Injector diagram

You can image the Injector like a bus, which is filled with all the instances of all our Services. When a component — in this example MyComponent aka the Client — ask for a specific class — in this case MyService aka the dependency the Injector will search within this bus and instance that can satisfy the client request. The founded dependency will be provided to the client, eventually.

In the same StackBlitz above you can see how IoC can be achieve in TypeScript, using inversify — one of the external library available in the scene.

I will not go in deep of how inversify works in detail, because it’s not our focus. You just need to understand that, under the hood, it does what you read previously.

Here is an updated version of the code that makes use of inversify

main.ts with inversify

import { Counter } from './counter.ts';
import { ElementHandler } from './element.ts';
import { Container } from 'inversify';

const container = new Container();

container.bind<Counter>(Counter).toSelf();
container.bind<ElementHandler>(ElementHandler).toConstantValue(new ElementHandler('#counterIoC'));

const counterIoC = container.get(Counter);

counterIoC.setUpCounter();

Something is starting to cook here and the things begin more technical. One question could easily arise…

Which are the advantages of DI?

Well, that’s a really good question my friend.

You can identify three major advantages:

  1. Modularity,
  2. Testing,
  3. Readability and Maintainability.

Modularity

DI promotes modularity by decoupling components, making it easier to replace or update individual parts of the system.

This is also the S of the SOLID principles, which stands for Single-responsibility principle — which means that a class should do one job, and one job only. Nothing more, nothing less.

You are going to encounter all the letters of the SOLID principles, because they are the pillars for a clean code and a clean architecture.

Testing

It facilitates unit testing by allowing dependencies to be mocked or substituted during testing.

Remember that you are working with, hopefully, small classes that do one job only — they are easier to test as a consequence!

Readability and Maintainability

Code becomes more readable as dependencies are explicitly defined and separated from the client’s logic.

Our smaller class are handy here as well. Less code to read and to understand and, if you want to explore the connected dependencies, they are all listed in an explicit way.

Now… you saw the actors that are acting in this movie. Understood how does it work with our special guest Liam Neeson. Examinated the advantages, but…

How does it work within Angular?

This will be our next topic, that will be addressed in a dedicated article that you will find here in some days.

Thanks for sticking with me and hope that everything is clear so far.

The journey will continue and i will be glad if you want to enjoy the ride with me till the last article of the serie and maybe explore more of mine.

See you in the next one,
G.

--

--

Giorgio Galassi

Angular developer. 💻 Proud member and manager of Angular Rome community! 🚀