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

Code Samples in TypeScript

I wrote the code samples in TypeScript because I am a big fan of the language. This does not mean you have to use it when building applications in Angular 2. The framework works great with ES5 and ES6.

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 DI 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.

As with Angular 1, we need to tell the framework how to create the service. In Angular 2, the directive and component decorators are the place where you configure dependency injection bindings. In this example, the App 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 component 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:

We use the require property to get access to the tab controller. Then, we use scope to get access to the pane controller. And, finally, we use the link function to connect the two. This is quite a bit of work for such a simple scenario.

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

The panes are always in order.

The query 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:

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.

class Login {
constructor(@Optional() service: LoginService) {}
}

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.

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.

View Bindings

The following makes LoginService available for injection in both the public children (a.k.a. light dom children) of App and its view.