Should I use the Angular inject function? 💉


One of the powers of Angular is dependency injection.

And in the early days of Angular we used the Typescript constructor to inject the dependencies we needed.

fun-3d-cartoon-teenage-boy-with-syringe

Let's say we needed to inject a custom UserService. We would do that via the constructor.

import { Component } from '@angular/core';
import { UserService } from '../services/user.service';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [],
  template: `Hello World`
})
export class AppComponent {
    constructor(public userService: UserService) { } 
}

But that has recently changed with the introduction of the inject function.

It can now look like this.

import { Component, inject } from '@angular/core';
import { UserService } from '../services/user.service';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [],
  template: `Hello World`
})
export class AppComponent {
    const userService = inject(UserService); 
}

What exactly is dependency injection in Angular?

Dependency injection as a general principal is common among many different frameworks.

ASP.NET Core would be a popular example.

We could also call it a modern architecture choice.

Before dependency injection existed developers were taught that if a part of the code needs another thing, it is responsible for creating or getting that thing.

dojob

So, for example, if I'm writing an Angular component that needs to display data from an end-point I'll also need code that makes the HTTP request and parses the response. That HTTP request code is "a dependency" because my "bigger" piece of code depends on it.

In really old programs, all of that code would be in more or less the same place. But for about 60 years now code has been separated into what we will oversimplify and call "modules". That way bits of it can be used and reused in many parts of a program and/or tested by itself.

So in my code, I'd have a HTTP service and my Angular component depends on it. Without dependency injection, I'd do something language specific to make my component code refer to the HTTP service. For example, I'd probably manually create a new instance of the HTTP service object.

But with dependency injection, I'd instead write my component in a way that says, "I need someone ELSE to give me a HTTP service module to use." That "someone else" is the thing that "injects" the dependency.

In practical terms dependency injection is supposed to make large-scale programs a little easier to maintain by making them easier to manage and control dependencies. That way in theory if things change, the developer only has to change some "dependencies" and maybe not the modules that depend on them.

So why should you use the new inject function in Angular?

Aside from being the new shiny way to do things are there any other good reasons you should use the inject function in Angular?

Is there a better reason besides "This way looks better then that way"?

I will have to admit that the main thing for me pretty much is that the inject function pretty much looks and feels nicer.

Another benefit is that you don't have to type out the constructor in your components.

And it is also more verbose in the way as to what's actually happening, making injection easier to understand for beginners.

By using the inject function instead of the constructor it gives new coders a better intuition as to what is going on, even though they might not fully understand dependency injection.

Migrating to the inject function

With the release of the Angular inject function they also rolled out a schematic to migrate your code.

You can run it with one simple command.

ng generate @angular/core:inject

Are there any downsides to the Angular inject function?

While the inject function has been well received in the Angular community it has also been criticized.

No new change comes without it's downsides.

There are two potential downsides of using the inject method to be considered:

  1. The dependencies are maybe a bit less obvious. Before the inject function you only needed to look at was at the constructor arguments and every injected dependencies would be there. Now they could be scattered around the constructor arguments and defined properties above the constructor. Not necessarily a big problem but sometimes we can miss the big picture, especially in code reviews and if the class is big (which could be another problem in itself).

  2. When it comes to testing. If you are using a TestBed, it's pretty straightforward as nothing really changes. But it is not uncommon to write isolated tests for pipes, directives or services (even components sometimes), when you are not testing in a TestBed environment, but are simply instantiating the class directly while providing the mocked dependencies directly in the constructors arguments.

Let's inject!

Some claim that many other DI frameworks tried similar techniques to the inject function and backed off it again.

They are now encouraging constructor injection again.

But personally I like the turn the Angular team took by the inject function, I like it.

Even if others say that it feels like a shortcut to me and shortcuts tend to encourage anti patterns

Till the next injection,

signature

Daniel Kreider - Angular Developer

Credits

Image by julos on Freepik