# Angular Dependency Injection: A Beginner’s Guide

If you’ve made your way to this article, you’ve likely heard of Angular, Google’s popular framework for building web applications. One challenging but critical topic that you’ll inevitably encounter as you delve deeper into Angular is Dependency Injection (DI). So, buckle up as we’re about to embark on an exciting journey to understand DI, its advantages, and how to use it in Angular.

### **What is Dependency Injection?**

In simple terms, DI is a design pattern that enables a class to receive dependencies from an external source rather than creating them itself.

Imagine a scenario where you’re constructing a house. You could either create every item (bricks, cement, windows, doors, etc.) yourself or get them delivered from various suppliers. The latter approach is similar to DI: it’s all about getting ‘dependencies’ (i.e., services or objects) ‘injected’ into a class.

```typescript
class House {
  constructor() {
    this.brick = new Brick();
    this.cement = new Cement();
  }
}
```

In the code snippet above, `House` creates its own dependencies. Here are some reasons why this is problematic:

1. **Testing difficulty**: In this approach, when you want to test the `House` class, you are forced to also test with `Brick` and `Cement` objects. You can't replace these dependencies with mock objects for testing, because the `House` class is directly instantiated with new `Brick` and `Cement` instances.
    
2. **Less flexibility**: The `House` class is rigidly configured to use `Brick` and `Cement`. What if later you want to construct a `House` with `Stone` and `Clay` instead? You'd need to modify the `House` class itself, which isn't ideal, especially if the `House` class is used widely in your application.
    
3. **Code reusability**: By creating dependencies within the class, it becomes hard to reuse them across different classes or modules. For instance, if you need to use the same `Brick` and `Cement` instances in another class, you cannot do so without creating new instances.
    

Now, let’s see how Dependency Injection could change this.

### **Why Dependency Injection?**

In the code snippet below, `House` is no longer responsible for creating `Brick` or `Cement`. It simply declares what it needs, and it's up to the system (in Angular's case, the injector) to provide those dependencies. This makes your code much easier to manage and evolve over time.

```typescript
class House {
  constructor(brick: Brick, cement: Cement) {
    this.brick = brick;
    this.cement = cement;
  }
}
```

Dependency Injection helps to solve the problems identified above. Let’s discuss how DI addresses each of those problems.

1. **Testing made easier**: With DI, testing becomes easier because you can inject mock versions of your dependencies during testing. For instance, if `Brick` and `Cement` services make HTTP requests to a server, and you want to isolate `House` from these server requests during testing, you can provide mock versions of `Brick` and `Cement` that do not make actual HTTP requests.
    
2. **Flexibility**: DI provides flexibility to your classes. If the `House` class needs to use `Stone` and `Clay` instead of `Brick` and `Cement`, you can simply change the services you inject without needing to change the `House` class itself. This way, `House` is less concerned about the specific implementation of the dependencies it uses and more focused on how it uses them. This is also known as the Dependency Inversion Principle: depend on abstractions, not on concrete classes.
    
3. **Code reusability**: DI promotes code reusability. Once `Brick` and `Cement` services are created, they can be injected wherever required, avoiding the need to create new instances every time. This makes your code cleaner and more efficient.
    

## **Dependency Injection in Angular**

Now that we understand the basics of DI let’s see how it works in Angular. Angular’s DI framework provides dependencies to a class upon instantiation. These dependencies are typically services that provide functionality such as fetching data from a server or logging user interactions.

To start with, let’s define a simple service called `LogService` that logs messages to the console.

```typescript
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class LogService {
  log(message: string) {
    console.log(`LogService: ${message}`);
  }
}
```

Next, let’s inject this service into a component called `AppComponent`.

```typescript
import { Component } from '@angular/core';
import { LogService } from './log.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  title = 'Hello Angular!';

  constructor(private logService: LogService) {
    this.logService.log(this.title);
  }
}
```

In the code above, the Angular DI system injects an instance of `LogService` into `AppComponent` via its constructor. We can then use this service to log a message to the console.

### **The @Injectable Decorator**

You might have noticed the `@Injectable` decorator on `LogService`. This decorator tells Angular that this service might itself have dependencies. Even though our `LogService` doesn't have any dependencies at the moment, it's good practice to add `@Injectable` in preparation for future needs.

### **Providers and Injector**

Two key concepts in Angular DI are providers and injectors. These two concepts are integral to Angular’s Dependency Injection (DI) system, but they can sometimes be a bit tricky to understand. To make it easier, think of the injector as a bakery and the provider as the recipe for creating a service.

### **Providers: Your Service’s Recipe**

A provider is like a recipe for creating an instance of a service. It tells the injector how to create or obtain that service. It’s typically the service class itself. Let’s consider an example where we have a service called `UserService`. This service might look like this:

```typescript
@Injectable({
  providedIn: 'root',
})
export class UserService {
  getUsers() {
    return ['John', 'Jane', 'Bob'];
  }
}
```

Here, `UserService` is a provider because it's the recipe for creating an instance of the `UserService`. The `@Injectable` decorator tells Angular that this class can be used as a provider.

### **Where Do We Define Providers?**

We usually define providers in Angular modules or components using the `providers` property. When you define providers, Angular creates an injector with all those providers. When a class needs a service, the injector checks its container of instances to see if it already has one to reuse. If not, the injector makes a new one using the provider.

```typescript
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [UserService] // UserService is the provider
})
export class AppComponent { ... }
```

In our `UserService` example above, we actually used a shortcut with `providedIn: 'root'`. This automatically provides the `UserService` in the root injector, which is like the main bakery for the whole app. This means that `UserService` will be available anywhere in our app.

### **Injectors: The Bakery**

An injector is a mechanism that is responsible for creating instances of services and injecting them into classes like components, other services, etc. Think of the injector as a bakery. If you ask it for a cake (`UserService` in our example), it will look to see if it has a cake already made. If it does, it gives you that cake. If it doesn't, it uses the recipe (provider) to make a new cake and then gives you the cake.

Here’s an example of how an injector might be asked for a service:

```typescript
import { Component } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  users: string[];

  constructor(private userService: UserService) {
    this.users = userService.getUsers();
    console.log(this.users);
  }
}
```

In this example, `AppComponent` asks the injector for `UserService`. The injector then checks if it has an instance of `UserService` to give. If it does, it will provide that instance. If it doesn't, it will use the `UserService` provider (recipe) to create a new instance and then give that to `AppComponent`.

The injector in Angular is not explicitly defined by developers in the code. Rather, it’s implicitly created and managed by the Angular framework itself. Angular creates a root injector for the application when it bootstraps the application, using the providers defined in the root module (`AppModule` by convention).

In Angular, an injector is created for every Angular module and component. So when you define a module or a component, Angular implicitly creates an injector for it.

If you provide a service at the component level, Angular will create a new instance of the injector for that component and its child components. This instance will be different from the one associated with the root or any other module. This allows Angular to have a hierarchical injector system, which means you can override service instances and control the scope of services.

To recap, providers and injectors work together to make Angular’s Dependency Injection system work. Providers are like recipes that tell the injector how to create or find an instance of a service. The injector is like a bakery that takes in these recipes and uses them to create and provide these instances. This whole process allows us to write modular, reusable, and testable code in our Angular applications.

**Further Reading**

[https://angular.io/guide/dependency-injection](https://angular.io/guide/dependency-injection)
