DEV Community


Angular DI in action - series

I am a Software Dev Engineer at One Identity ☀️
・3 min read


In Angular, you can use services for several purposes. You can share state between components, share common functionalities, create plus one abstraction layer for some problem and so on. Services are just basic classes with the @Injectable() decorator. But how does this decorator actually works, what are the resolution rules that describes which service to use in a component?

Let's dive into these topic. This will be a series with 4-6 parts, and this is the first one.

What is DI anyway?

Dependency Injection is a design pattern, where the initiated class request its dependencies from external sources instead of creating them by itself. With this, you can increase modularity, flexibility and reusability of your codebase.

Starting point

Let's create a demo service for our starting point. It does not matter what this service can do, because we will discover how Angular handles dependency injection.

import { Injectable } from "@angular/core";

export class DemoService {
  constructor() {}

Enter fullscreen mode Exit fullscreen mode

Provide possibilities of a service

So if you would like to use this service, what are your options to tell Angular

I need this service, so inject this for me?

There are two popular ways - as of my current experiences.

  1. Providing it on root level with providedIn: 'root'
  2. Providing it on module level

These two uses almost the same technique with a slight difference. But are these two is used because these are the best solutions, or there are some convenient reasons behind this?

Angular modules never got destroy, so neither the services provided on them 1.

It not obviously a problem, but good to know this. :)

Dependency lookup mechanism

When you are providing a service, you are requesting that resource from the DI. Dependency injection is solved in the background for you by Angular, but it is worth to have a bit of knowledge how it works.

First of all, there are three different hierarchical injectors (that you can configure):

  1. ModuleInjector: @NgModule and @Injectable with providedIn - module based
  2. ElementInjector: empty by default, but sticked to a component
  3. root ModuleInjector: created when bootstrapping the application

and there are two more that you can not configure:

  1. NullInjector(): this is the top/lowest one
  2. PlatformModule's ModuleInjector() - provide special core things

Visualised, it looks like the following - taken from the official docs:



ModuleInjector is created with providers and import properties in any of the @NgModules. At the end, this is a flattening of all of your providers of all the modules that can be reached by any of the imports, recursively.

Think of it as a huge basket of providers - configured by you!

Child ModuleInjectors are created when you lazy load a new module.


Angular creates an ElementInjector for every DOM node implicitly. You can configure this injector with the providers or viewProviders property of a component decorator.


This is the parent of every Injector in the hierarchy.
If you reach this point - without any modifier - an error will be thrown for you. Yes, that well know error:

NullInjectorError: No provider for XYZService

Why is it important?

So you may have asked yourself now: why is it important for us at all? I can tell you the reason: with understanding the base mechanism of injectors, we can dive a bit deeper to understand how Dependency Injector traverse up the hierarchical graph of this Injectors and how can we influence this lookup mechanism.

With this, you can easily understand why you have a NullInjectorError, why your components have a different instance of your service.

This may help you to debug faster and find the solution for your problem easier.

Stay tuned

This was the first part of this series. In the following one, we will check the resolution rules of dependencies as well as the first resolution modifier.

Have a nice day! :)


Discussion (0)