Dependency Injection in Angular

Angular: The Full Gamut Edition

Charlie Greenman
September 10, 2020
5 min read

What Dependency Injection Solves

Dependency Injection from an Angular perspective, pragmatically only solves one real issue. That one issue is Unit Testing. That is, because in an Angular/Typescript setting a developer will have the ability to import and export a file. So there already is a way of decoupling different services/ classes from each other.

import { PostsFacade } from '@razroo/razroo/data-access/posts';  

@Component({
  selector: 'razroo-blog',
  templateUrl: './blog.component.html',
  styleUrls: ['./blog.component.scss']
})
export class BlogComponent implements OnInit {
  posts: any[];
  allPosts$: Observable<Post[]> = new PostsFacade().allPosts$;

  constructor() {}

  ngOnInit() {}
}

In the above, our class doesn’t control how this value in injected. It goes straight from the import into our class. However, if we were to do something like this:

import { PostsFacade } from '@razroo/razroo/data-access/posts';  

@Component({
  selector: 'razroo-blog',
  templateUrl: './blog.component.html',
  styleUrls: ['./blog.component.scss']
})
export class BlogComponent implements OnInit {
  posts: any[];
  allPosts$: Observable<Post[]> = this.postFacade.allPosts$;

  constructor(private postsFacade: PostsFacade) {}

  ngOnInit() {}

}  

Now it is our BlogComponent class that is controlling how the postsFacade is getting passed through. This makes things particularly easy when it comes to unit testing. It allows us to override the value in our unit test, and create mocks for all services. We will discuss more on this in the chapters in the chapters involving unit testing, but I just wanted to bring up the main reason behind unit testing here.

Less obvious, and specifically if the providedIn is used, is dependency injection also helps keep bundle sizes compact. This is done by tree shaking, which refers to the compiler removing code from the final app if it is not actually referenced by the app. If we do not use providedIn, tree shaking will not be done. In addition, as we discussed above, it allows us to keep our configurations separate. While the ability to export/import is an option within Typescript, dependency injection, allows the framework to be completely aware of everything being used within the framework. So, besides unit testing, this architecture can be useful for using tokens that contain a particular value, and then being overridden depending on the environment of application(e.g. development vs. production).


Real World Example

Creating Injectable Service

import { Injectable } from '@angular/core';
@Injectable({ 
  providedIn: 'root',
})
export class PxCodeService {
  constructor() { } 
}

This code right here is doing two things:

  1. It is saying that this service is an Injectable.
  2. It is saying that this injectable should be provided in the root(aka the AppModule).

Now would be a good time to discuss what the providedIn property does. It accomplishes two things:

  1. Angular creates a single, shared instance of the service and injects it into any class that asks for it. So, there is no need to insert it as a provider for your module, and you can simply pull it into your class whenever you want. a
  2. It also allows Angular to optimize an app, by removing the service from the compiled app, if it isn’t used.

Including Injectable Service in Component

Now if we would like to include this service in our component, we would do the following:

// code-box.component.html
<div *ngFor="let codeBox of codeBoxes"> 
  {{codeBox.data}}
</div>
import { Component }   from '@angular/core';
import { CodeBox }        from './code-box.interfaces';
import { PxCodeFacade } from './px-code.facade';

@Component({
  selector: 'px-code-box',
  template: './code-box.component.html',
  styles: ['./code-box.component.scss'],
})
export class HeroListComponent {
  codeBoxes: CodeBox[];

  constructor(pxCodeFacade: PxCodeFacade) {
    this.codeBoxes = pxCodeFacade.getCodeBoxes();
  }
}

Now would technically be a good time on discussing how to test these mocked dependencies, however it gets a little bit complicated. How to mock dependencies, will be discussed in the section on unit testing.


Services that need other services

Services can have their own dependencies. If we wanted to inject a service, into our service, it would be as simple as doing the following:

import { Injectable } from '@angular/core'; 
import { Logger } from '../logger.service';

@Injectable({ 
  providedIn: 'root',
})
export class PxCodeService {
  constructor(private logger: Logger) { }
  getLog () {
    this.logger.log('getting codeboxes');
  } 
}

As we can see in the above, our injected service is taking another injected service. By simply passing it into the constructor, similar to how we do for our components, we can use it within our application.


Dependency Injection Token

Internally Angular uses dependency injection tokens for injectable services, to reference what injectable it is using. As an Angular developer, we also have the option to use these tokens directly within our app. There are two different ways of providing tokens in Angular:

  1. Strings
 {provide: 'EmailService', useClass: MandrillService}
  1. Type Tokens
{provide: EmailService , useClass: MandrillService}
  1. Injection Tokens
new InjectionToken{provide: EmailService , useClass:}

The benefit of this approach is that it avoids name clashes.

Tokens can be a useful way of providing a default value to be used across the app, hijacking the value, and providing something else, in the scenarios where it is not of use. An example of such is within the Angular Material Components. By default, the MAT_DATE_LOCALE will use the existing LOCALE_ID. However, if you would like to override the date locale across the app, all that needs to be done is to override the MAT_DATE_LOCALE token.

@NgModule({
  providers: [
    {provide: MAT_DATE_LOCALE, useValue: 'en-GB'},
  ],
})
export class MyApp {}

This allows for numerous components across the app, using the same token to be overridden.


Wrapping Up

The intent of this chapter, is to introduce the concept of dependency injection in Angular. In addition, present the scenarios in which it is used in a regular enterprise application. Dependency injection as a pattern can be very complex, and I feel looking at the documentation, the majority of use cases tend not to be used. So I wanted to keep this simple. Unit testing, the other widely used part of dependency injection, will be discussed in a separate chapter on unit testing.

More articles similar to this

footer

Razroo is committed towards contributing to open source. Take the pledge towards open source by tweeting, #itaketherazroopledge to @_Razroo on twitter. One of our associates will get back to you and set you up with an open source project to work on.