Always use "inject"
Tags: angular, typescript, javascript
Published: 2022-12-02 08:23:20

_Original cover photo by [Markus Spiske](https://unsplash.com/@markusspiske) on Unsplash._


## Introduction

Starting from Angular version 14, a function named "inject" became available for developers to use. This function is used to inject dependencies into injection contexts, meaning anything that is used inside components, directives, and so on: whenever dependency injection via a constructor is available. This allows the developers to write functions that can both be reused by components *and* use dependency injection. For example, we can use references to the router data in functions to simplify sharing data:

```typescript
import { ActivatedRoute } from '@angular/router';

function getRouteParam(paramName: string): string {
  const route = inject(ActivatedRoute);
  return route.snapshot.paramMap.get(paramName);
}

@Component({
  selector: 'app-root',
    template: `
        <h1>Route param: {{ id }}</h1>
    `,
})
export class AppComponent {
  id = getRouteParam('id');
}
```

As you can see, we now can use this function to access route parameters anywhere without the need to inject the `ActivatedRoute` into the components everytime. 

Of course, this is a powerful tool, but with its arrival, a question arises:

> Should we ditch the constructor and use `inject` everywhere?

So let's discuss pros and cons.

## Pros

### 1. Reusability

As we've seen, we can use `inject` to share code between components. This is a huge advantage, as we can now write functions that can be used in multiple components without the need to inject the same dependencies over and over again. This is especially useful when we have a lot of components that use the same dependencies. Now, if we have a piece of reusable logic that relies on some injection token (a service, a value or something else), we are no longer restrained to using classes; we can just write functions, which are arguably a simpler and more flexible way to write code. (this is an opinion).

### 2. Type inference

Previously, when we created a class that used some injection token, we had to explicitly define the type of the property that would hold the injected value. This is no longer necessary, as the type of the injected value is inferred from the type of the injection token. This is especially useful when we utilize the `InjectionToken` class, which we can now use without the `@Inject` decorator. Here, take this as an example:

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

const TOKEN = new InjectionToken<string>('token');

@Component({
    // component metadata
})
export class AppComponent {
    constructor(
        @Inject(TOKEN) token: string,
        // we had to explicitly define 
        // the type of the token property
    ) {
        // ...
    }
}
```

This is both more verbose *and* depends on what we put as the type of `token` explicitly. Now, we can just write:

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

const TOKEN = new InjectionToken<string>('token');

@Component({
    // component metadata
})
export class AppComponent {
    private token = inject(TOKEN);
    // type "string" is inferred
}
```

Of course, the same type inference works on Services, and pretty much anything that can be injected.

### 3. Easier inheritance

Estending Angular components/directives and so on from other classes has always been kinda painful, especially if we use injected dependencies. Problem is that with constructor injection we have to pass the dependencies to the parent constructor, which requires writing repetitive code and becomes the harder the longer the inheritance chain. Take a look:

```typescript
export class ParentClass {
    constructor(
        private router: Router,
    ) {
        // ...
    }
}

@Component({
    // component metadata
})
export class ChildComponent extends ParentClass {
    constructor(
        // we have to inject all the 
        // parent dependencies again
        // and add others
        private router: Router,
        private http: HttpClient,
    ) {
        super(router); // also pass to the parent
    }
}
```

With inject, we can essentially skip the constructors whatsoever, and just use the `inject` function to get the dependencies. This is especially useful when we have a long inheritance chain, as we can just use `inject` to get the dependencies we need, and don't have to worry about passing them to the parent constructor. Here's how it looks:

```typescript
export class ParentClass {
    private router = inject(Router);
}

@Component({
    // component metadata
})
export class ChildComponent extends ParentClass {
    private http = inject(HttpClient);
}
```

Let's now get to the final advantage usinjg `inject` brings:

### 4. Custom RxJS operators

With `inject`, we can create custom RxJS operators that use dependency injection. This is especially useful when we want to create a custom operator that uses a service, but we don't want to inject the service into the component and pass the reference to the operator. Here's an example without `inject`:

```typescript
function toFormData(utilitiesService: UtilitiesService) {
    return (source: Observable<any>) => {
        return source.pipe(
            map((value) => {
                return utilitiesService.toFormData(value);
            }),
        );
    };
}

@Component({
    // component metadata
})
export class AppComponent {
    constructor(
        private utilitiesService: UtilitiesService,
    ) {
        // ...
    }

    private formData$ = this.http.get('https://example.com').pipe(
        toFormData(this.utilitiesService),
    );
}
```

Now, passing a service each time we want to use this operator will become tedious, especially when the operator also requires other arguments. Now, let's see how it looks with `inject`:

```typescript
function toFormData() {
    const utilitiesService = inject(UtilitiesService);
    return (source: Observable<any>) => {
        return source.pipe(
            map((value) => {
                return utilitiesService.toFormData(value);
            }),
        );
    };
}

@Component({
    // component metadata
})
export class AppComponent {
    private formData$ = this.http.get('https://example.com').pipe(
        toFormData(),
    );
}
```

Nice and clean! Extendability of RxJS signifantly increases with this approach.

## Cons

Downsides to this approach are mostly minimal, but still worth mentioning.

### 1. Unfamiliarity

`inject` only recently became availsble as an API you can import and use, and it does not really have analogs in any other framework, so it might be unfamiliar to some developers. This is not a big deal, as it's easy to learn, and won't be a problem in the future. 

### 2. Availability 

The function is only available in dependency injection contexts, so trying to use it in model or DTO classes, for example, will result in errors. Read more about this in the [Angular documentation](https://angular.io/api/core/inject). This has a workaround using the `runInContext` API, with something like this:

```typescript
@Component({
    // component metadata
})
export class AppComponent {
   constructor(
    private injector: EnvironmentInjector,
   ) {}

   ngOnInit() {
       this.injector.runInContext(() => {
           const token = inject(TOKEN);
           // use the token freely outside of the constructor
       });
   }
```

Read more about this in an article by [Nethanel Basal](https://netbasal.medium.com/): [Getting to Know the runInContext API in Angular](https://netbasal.com/getting-to-know-the-runincontext-api-in-angular-f8996d7e00da).

### 3. Testing

Probably the biggest downside to this approach is that it makes testing a bit harder. If you have been creating instances of services for testing purposes using the `new` keyword to bypass using `TestBed`, you can't do it if the services use `inject`, making us bound to `TestBed`

## Conclusion

With each version, Angular provides developers with more and more different features and new approaches to solve problems. Hopefully, this article will help you to understand the new `inject` function and how to use it in your projects.