Using the Inject Function in Angular 15

Simplifying Code and Inheritance Dependencies in Angular Applications

Since Angular 14/15, there has been an alternative way to inject dependencies into our Angular applications.

I will show two cases of using the inject function with Angular 14/15.

Inject Dependencies in Functions

Using inject, we can create functions and inject dependencies without adding parameters in the function class.

Example: We create a function that needs the HttpClient to request data.

import {lastValueFrom, map} from "rxjs";
import {inject} from "@angular/core";
import {HttpClient} from "@angular/common/http";

export function getPlayers(): Promise<unknown> {
  return lastValueFrom(inject(HttpClient).get('https://www.balldontlie.io/api/v1/players').pipe(
    map((response: any) => {
      return response.data;
    })
  ))
}

We can use the function in another class in the constructor lifecycle:

  constructor() {
    getPlayers().then(p => {
      this.players = p;
    })
  }

The inject() function must be called from an injection context such as a constructor, a factory function, or a field initializer.

Simplify Inheritance Dependencies

Another way to simplify and clean up the constructor in our classes is when we use inheritance. For example, when a class works as a base or superclass for others, like NbaService, which needs HttpClient in the constructor.

import {Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {map, Observable} from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class NbaService {

  constructor(private http: HttpClient) {

  }

  getPlayers(): Observable<any> {
    return this.http.get('https://www.balldontlie.io/api/v1/players').pipe(
      map((response: any) => {
        return response.data;
      })
    );
  }
}

When we create a new class like NcaaService and extend NbaService, we must provide the constructor dependencies.

import {Injectable} from '@angular/core';
import {NbaService} from "./nba.service";
import {HttpClient} from "@angular/common/http";
import {filter} from 'rxjs';

@Injectable()
export class NcaaService extends NbaService {
  constructor(private httpClient: HttpClient) {
    super(httpClient);
  }

  getListPlayers() {
    return this.getPlayers().pipe(
      filter((p) => p.league === 'NCAA')
    )
  }
}

What happens if the NbaService constructor changes?

export class NbaService {
  constructor(private http: HttpClient, private loader: LoaderService) {
  }
    ....

The NcaaService has to update the constructor and provide a dependency no related with him.

The easy way to solve this is to switch our dependencies from constructor to inject function.


export class NbaService {

  http = inject(HttpClient);
  loader = inject(LoaderService)

  showLoadingMessage() {
    this.loader.show();
  }
}

The extended classes don't have to provide dependencies anymore. This helps to simplify our code and solve a typical problem when we have to provide dependencies without needing to, only because our base code needs it.

@Injectable()
export class NcaaService extends NbaService {
  constructor() {
    super();
  }
}

Conclusion

Using the inject function in Angular applications can simplify the code and help to solve common problems related to dependency injection. With inject, we can create functions and inject dependencies without adding parameters in the function class, and we can simplify inheritance dependencies.