Using Angular’s awesome Dependency Injection

Using Angular’s awesome Dependency Injection

Dependency injection is a powerful tool … for reducing code complexity and properly sub-dividing your code into pure, maintainable bits. ... Dependency injection is a powerful tool not just for reducing memory load, but also for reducing code complexity and properly sub-dividing your code into pure, maintainable bits.

If you’ve ever used Angular for any amount of time, you’ve probably noticed how freaking awesome its dependency injection is. With just the invocation of the injectable decorator, you can pull in reusable instances of any service in your application just by referencing the type:

import {Injectable} from '@angular/core';
import {BackendService} from './backend.service';

export class AuthService {
      private backend: BackendService,
  ) {

Notice that I don’t have to instantiate the BackendService anywhere because it's already handled for me by Angular. This has the added benefit of ensuring that only one instance of the BackendService is created during runtime, which is good for memory!

Background — Flitter

One of the major philosophies in Flitter is that everything should be a class. So while Flitter incorporates many traditionally ES5 libraries — Express, Mongoose, Agenda.js, etc— it provides a system for defining their resources using ES6+ classes. That’s why in Flitter you’ll never see, for example, a schema-based model definition:

// Mongoose - from the getting started guide
var kittySchema = new mongoose.Schema({
  name: String

kittySchema.methods.speak = function () {
  var greeting =
    ? "Meow name is " +
    : "I don't have a name";

Instead, we define the appropriate class:

// Using Flitter classes
const Model = require('libflitter/database/Model')
class Kitty extends Model {
  static get __context() {
    return {
      name: String

  speak() {
    const greeting =
      ? `Meow name is ${}` : `I don't have a name`

Having all our resources defined in standard classes is subjectively much easier to maintain and reason around than a multitude of different schema formats and function calls. Plus it has the added benefit of enabling inheritance for all our resources out-of-the-box. However, it raises an interesting issue.

The Problem With Classier Services

If everything is a class, how do we access reusable methods from our application? If we were using objects, this would be easy:

// logHelpers.js
module.exports = exports = {
  loggingLevel: 2,
  out(what, level) {
    if ( !Array.isArray(what) ) what = [what]
    if ( level >= this.loggingLevel ) console.log(...what)
  error(what, level = 0) { this.out(what, level) },
  warn(what, level = 1) { this.out(what, level) },
  info(what, level = 2) { this.out(what, level) },
  debug(what, level = 3) { this.out(what, level) },

This seems to be easy to use. We just import the module and we’re good to go:

const logging = require('./logHelpers')
const someFunction = () => {'someFunction has executed!')

But what happens if we want to create a different helper “class” that will send e-mails if an error is logged? Well, we could just write the whole thing from scratch again, but that’s not very DRY. So instead, we override specific properties from the original helper:

// emailLogHelpers.js
const logHelpers = require('./logHelpers')
module.exports = exports = Object.assign(logHelpers, {
  emailOut(what, level) {
    this.out(what, level);
  error(what, level = 0) {  this.emailOut(what, level) },

But this introduces ambiguity. What does this refer to in error()? What about in emailOut()? Can you spot the error? It feels correct from an OOP standpoint. However, if we call error() on the email helpers object, we get the following error:

ReferenceError: out is not defined
  at Object.emailLogHelpers (emailLogHelpers.js:6:4)

Why? Because this in the emailOut function doesn't actually refer to the combined object, but rather the original object at the time of creation. That is, the right-side of the Object.assign call.

This could easily be resolved by defining a LogHelper class and creating a child class EmailLogHelper. Then, in the EmailLogHelper class, this would unambiguously refer to the instance itself, which already has all the LogHelper methods. For example:

// EmailLogHelper.js
const LogHelper = require('./LogHelper')
class EmailLogHelper extends LogHelper {
  out(what, level) {
    super.out(what, level);

But this leaves us with the root issue of class-based services:

Service classes must be instantiated before they can be used.

Why is this a problem? Well, what if there’s some config service that LogHelper relies on (or depends on) to get the logging level? In that case, to use the service anywhere we'd also have to instantiate the config service and pass that on to the log helper. But then we have 20 nearly-identical instances of the same class doing the same thing. What's the solution?

Dependency injection in ES6: Easier than you think!

The solution to all the problems above is to have some dependency injector manager class create instances of all the relevant services when the application starts. Then, whenever a class needs access to a service, it fetches the shared instance from the DI. This saves memory and prevents the manual dependency-chaining problem above.

It turns out that thanks to the niceness of the ES6 class syntax it’s pretty easy to implement a basic DI in vanilla JavaScript! We’re going to approach this in multiple parts.

The Service Class

A service (for our basic purposes) is just a class that should be instantiated once the first time it is needed, then re-used for subsequent calls. So the service class can be entirely bare for now:

class Service {


Eventually, you can make this system more advanced by doing fancy things like tracking service state or even making services themselves injectable! Perhaps in a future post, we’ll explore this.

The Injectable Class

This class will be the parent class of every class that can make use of automatic DI. In our Angular analogy, this is akin to the Injectable decorator. It should do two things: specify the services we want to access and provide a mechanism for accessing them. Here’s an example based on Flitter’s Injectable class:

// Injectable.js
class Injectable {
  static services = []
  static __inject(container) { => {
      this.prototype[serviceName] = container.getService(serviceName)

Obviously, this is missing some niceties and type-checking, but the basic functionality is there. Statically, we define an array of service names for the instance to access, then, at some point before the class is instantiated, the __inject method is called. This method injects the services instances into the class' prototype, which is the under-the-hood function that is copied for each instance of the class. It gets these service instances from a magical container which we'll cover shortly.

This makes it really easy for classes to access services. For example:

// DarkSideHelpers.js
const Injectable = require('./Injectable')
class DarkSideHelpers extends Injectable {
  static services = ['logging']
  doItAnakin() {
    try {
    } catch (error) {
      this.logging.error('It\'s not the Jedi way!')

No instantiation of the LogHelper class required! Isn't that nifty?

The Service Container

But fancy static will do nothing for us if there are no services to inject. We need to create a place for them to live. Because we want to reuse instances as much as possible, we need a container to create and manage those instances for us. This container should contain a mapping of service names to service classes, instantiate requested services if they don’t exist yet, and return these instances on request. Let’s try one:

// ServiceContainer.js
const LogHelper = require('./LogHelper')
const EmailLogHelper = require('./EmailLogHelper')

class ServiceContainer {
  constructor() {
    // We define the service classes here, but we won't
    // instantiate them until they're needed.
    this.definitions = {
      logging: LogHelper,
      emailLogging: EmailLogHelper,

    // This is where the container will store service instances
    // so they can be reused when requested.
    this.instances = {}

  getService(serviceName) {
    // Create a service instance if one doesn't already exist.
    if ( !this.instances[serviceName] ) {
      const ServiceClass = this.definitions[serviceName]
      this.instances[serviceName] = new ServiceClass()
    return this.instances[serviceName]

Our bare-bones service container contains a list of service definitions and a method for retrieving service instances. It satisfies our requirements because it won’t instantiate a service until it’s needed but will reuse existing instances. So we have the container for our services, but now we the final piece to make all three parts work together.

The Dependency Injector Host

The DI host is the boss of the whole operation. It’s responsible for creating an instance of the container (or even multiple different instances) and keeping track of them. It is also responsible for calling the magic __inject method on classes before they're instantiated. There are several different strategies for how this can be done. Each has its own merit based on the situation, but we'll look at one that works well for standalone applications:

// DependencyInjector.js
const ServiceContainer = require('./ServiceContainer.js')
class DependencyInjector {
  constructor() {
    this.container = new ServiceContainer()

  // Injects the dependencies into an uninstantiated class
  make(Class) {
    return Class.__inject(this.container)

Like the rest of our classes, this one is pretty straightforward. When constructed, it creates a new service container. Then we can pass in classes to the make method and it will inject dependencies from the container into the class.

The DI instance should be shared across your entire application. This will help re-use service instances as much as possible. Here’s a silly example.

A spam-generating example app

As an example of how to use this system, let’s create a very basic application. This application should repeatedly send spam email to its owners on run. We’re going to use our fancy dependency injector for this.

// App.js
const Injectable = require('./Injectable')
class App extends Injectable {
  static services = ['emailLogging']
  run() {
    setInterval(() => {
      this.emailLogging.error('Haha made ya\' look!')
    }, 5000)

We tie it all together with our DI and run the app:

// index.js
// Create the dependency injector instance for the application
const DI = require('./DependencyInjector')
const di = new DI()

// Now, create the instance of our application using the DI to inject services
const App = di.make(require('./App'))
const app = new App() // Has access to the emailLogging service;


Dependency injection is a powerful tool … for reducing code complexity and properly sub-dividing your code into pure, maintainable bits.

We’ve built a basic application with injectable dependencies in vanilla ES6. Obviously, there are a lot of enhancements and improvements that could be made here. For example, type checking for services and containers, making the services themselves injectable, moving the Injectable base class to an ES7 decorator so it can be applied to classes with other parents, etc. Perhaps I'll do a follow-up in the future. But I hope this article was a good illustration of the power of DI in making code nicer.

Dependency injection is a powerful tool not just for reducing memory load, but also for reducing code complexity and properly sub-dividing your code into pure, maintainable bits.

Thanks for making it this far.

JavaScript Angular ES6 Dependency

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Install Angular - Angular Environment Setup Process

Install Angular in easy step by step process. Firstly Install Node.js & npm, then Install Angular CLI, Create workspace and Deploy your App.

Basics of Angular: Part-1

What is Angular? What it does? How we implement it in a project? So, here are some basics of angular to let you learn more about angular. Angular is a Typesc

How to Build an Angular Application with Angular CLI

How to set up the Angular CLI and generate a Trivial App

Angular 9 Dependency Injection Example Tutorial

Angular ships with one design pattern and its own dependency injection framework. Dependencies are services that a class needs to perform its function.

Angular Dependency Injection: Multi Providers

Angular’s dependency injection mechanism enables you to easily “plug-in” external functionalities. Instead of creating instances of dependencies yourself, you leave this task to the dependency injection mechanism.