Dependency Injection in Angular 1 and Angular 2.x

Victor Savkin
Angular
Published in
6 min readSep 12, 2016

--

Victor Savkin is a co-founder of nrwl.io, providing Angular consulting to enterprise teams. He was previously on the Angular core team at Google, and built the dependency injection, change detection, forms, and router modules.

In this article I will show how common scenarios of using dependency injection in Angular 1 can be implemented in Angular 2.

Let’s Start with a Simple Component

Let’s start by implementing a simple login component.

Now, the same component implemented in Angular 2.

Experienced developers know that coupling of the login component to the login service is problematic. It is hard to test this component in isolation. And it also makes it less reusable. If we have two applications, with two login services, we will not be able to reuse our login component.

We can remedy it by monkey patching the system loader, so we can replace the login service, but this is not the right solution. We have a fundamental problem with our design, which dependency injection can help us fix.

Using Dependency Injection

We can solve our problem by injecting an instance of LoginService into the constructor instead of creating it directly.

Now, we need to tell the framework how to create an instance of the service.

All right, let’s port this example to Angular 2.

As with Angular 1, we need to tell the framework how to create the service. In Angular 2 we can do it by adding the service to the list of providers.

NgModule is the “default” place to configure dependency injection providers. In this example a single instance of the login service will be created, and it will available for the login component and any of its descendants.

Scoping DI to a Component Subtree

Dependency injection in Angular 2 is more flexible. Sometimes you don’t want to the service to be available for all the components bootstrapped from a module. Rather you would like it be scoped to a particular subtree. You can do that by adding a provider to the directive or component decorators.

In this example, the AppPart component makes LoginService available for itself and all its descendants, including the login component. An instance of the login service will be created next to the App component. So if multiple children depend on it, all of them will get the same instance.

We separated the two concerns: the login component now depends on some abstract login service, and the app module creates a concrete implementation of the service. As a result, the login component no longer cares what implementation of the login service it will get. This means that we can test our component in isolation. And we can use it in multiple applications.

Note, that Angular 1 relies on strings to configure dependency injection. Angular 2 by default uses type annotations, but there is a way to fall back on strings when more flexibility is required.

Using Different Login Service

We can configure our application to use another implementation of the login service.

Configuring Login Service

One of the great things about dependency injection is that we do not have to worry about the dependencies of our dependencies. The login component depends on the login service, but it does not need to know what the service itself depends upon.

Let’s say the service requires some configuration object. In Angular 1, it can be done as follows:

Now, the Angular 2 version:

Injecting the Component’s Element

There is often a need for a component to interact with its DOM element. This is how it can be done in Angular 1:

Angular 2 does a much better job here. It uses the same dependency injection mechanism to inject the element into the component’s constructor.

Injecting Other Directives

It is also quite common to have multiple directives working together. For instance, if you have an implementation of tabs and panes, the tab component will need to know about the pane components.

This is how it can be done in Angular 1.

We use the “require” property to get access to the “tab” controller.

And, once again, Angular 2 does a better job here.

But we can do even better than that! Instead of panes registering themselves with the closest tab, the tab component can get the list of panes using the ContentChildren decorator.

Query takes care of many of the problems developers face when implementing this in Angular 1:

  • The panes are always in order.
  • The QueryList will notify the tab component about changes.;
  • There is no need for Pane to know about Tab. The Pane component is easier to test and reuse.

Single API

Angular 1 has several APIs for injecting dependencies into directives. Who hasn’t been confused by the difference between “factory”, “service”, “provider”, “constant” and “value”? Some objects are injected by position (e.g., element), some by name (e.g., LoginService). Some dependencies are always provided (e.g., element in link), some have to be configured using “require”, and some are configured using parameter names.

Angular 2 provides a single API for injecting services, directives, and elements. All of them get injected into the component’s constructor. As a result, there is a lot less API to learn. And your components are much easier to test.

But how does it work? How does it know what element to inject when a component asks for one? The way it works is as follows:

The framework builds a tree of injectors that matches the DOM.

<tab><pane title=”one”></pane><pane title=”two”></pane></tab>

The matching injector tree:

Injector matching <tab>
|
|__Injector matching <pane title=”one”>
|
|__Injector matching <pane title=”two”>

Since there is an injector for every DOM element, the framework can provide contextual or local information, such as an element, attributes, or nearby directives.

This is how the dependency resolution algorithm works.

So if Pane depends on Tab, Angular will start by checking if the pane element happens to have an instance of Tab. If it does not, it will check the parent’s element. It will repeat the process until either it finds an instance of Tab or it reaches the root injector.

You can point to any element on the page, and by using ngProbe get its injector. You can also see an element’s injector when an exception is thrown.

I know that it may seem a little bit complicated, but the truth is Angular 1 already has a similar mechanism. You can inject nearby directives using “require”. But this mechanism is undeveloped in Angular 1, and that is why we cannot not fully take advantage of it.

Angular 2 takes this mechanism to its logical conclusion. And turns out we do not need the other mechanisms any more.

Advanced Examples

So far, we have looked at examples that worked in both Angular 1 and Angular 2. Now, I want to show you a few advanced examples that just cannot be expressed in Angular 1.

Optional Dependencies

To mark a dependency as optional, use the Optional decorator.

Controlling Visibility

You can be more specific where you want to get dependencies from. For instance, you can ask for another directive on the same element.

Or you can ask for a directive in the same template.

Providing Two Implementations of the Same Service

Since Angular 1 has only one injector object, you cannot have two implementations of LoginService in the same app. In Angular 2, where every element has an injector, this is not a problem.

The services and directives created under SubApp1 will use CustomPaymentService1, and the ones created under SubApp2 will use CustomPaymentService2, even though all of them declare a dependency on PaymentService.

Summary

  • Dependency injection is one of the core parts of Angular 2.
  • It allows you to depend on interfaces, not concrete types.
  • This results in more decoupled code.
  • This improves testability.
  • Angular 2 has one API for injecting dependencies into components.
  • Dependency injection in Angular 2 is more powerful.

Read More

Victor Savkin is a co-founder of Nrwl. We help companies develop like Google since 2016. We provide consulting, engineering and tools.

If you liked this, click the 👏 below so other people will see this here on Medium. Follow @victorsavkin to read more about monorepos, Nx, Angular, and React.

--

--

Nrwlio co-founder, Xoogler, Xangular. Work on dev tools for TS/JS. @NxDevTools and Nx Cloud architect. Calligraphy and philosophy enthusiast. Stoic.