Angular Inject Function - Better Than Constructor
Angular Inject Function - Better Than Constructor

In this post you will learn how to use inject function inside Angular.

Starting from Angular 14 we have now inject function and we can inject our dependencies in another way. Let's have a look.

I already prepared a small service that we want to inject.

@Injectable({
  providedIn: 'root',
})
export class UsersService {
  getUsers() {
    return of([
      { id: '1', name: 'Foo' },
      { id: '2', name: 'Bar' },
    ]);
  }
}

Here we register it in root and we have a getUsers method which simply return some mock data.

How do we typically use such service inside our component?

export class AppComponent implements OnInit {
  constructor(private usersService: UsersService) {}

  ngOnInit(): void {
    this.usersService.getUsers().subscribe(console.log);
  }
}

We inject it inside constructor and use inside ngOnInit to get data.

Constructor

As you can see in console, we are getting our data from the service.

This is what we used previously and this is still a valid code. It is not deprecated even in the latest Angular.

Using inject

But we can write it in another way.

export class AppComponent implements OnInit {
  usersService = inject(UsersService)

  ngOnInit(): void {
    this.usersService.getUsers().subscribe(console.log);
  }
}

Here we didn't use constructor but a function inject to get UsersService inside our component.

Constructor

As you can see it works in exactly the same way.

With inject we are writing less code and it is more readable.

But just from the beginning I want to tell you super important point. We can't use it at any place of our application.

// This code won't work
export class AppComponent implements OnInit {
  ngOnInit(): void {
    const usersService = inject(UsersService)
    usersService.getUsers().subscribe(console.log);
  }
}

Here I injected UsersService inside ngOnInit and tried to use it. We don't get any errors from the Typescript but we get errors in the runtime.

Inject error

Inject must be called in the construction time

Which actually means that we are using inject only during creating of the component and not later. So we can't just throw it at any place of our code.

Moving code outside

Let's say that we want to inject ActivatedRoute and listen to the page query parameter and render it on the screen. How we can do that?

export class AppComponent implements OnInit {
  page$ = inject(ActivatedRoute).queryParams.pipe(
    filter((params) => params['page']),
    map((param) => params['page'])
  )
}

Here we created an observable page$ which we get through injection of ActivatedRoute. Now we can simply render it in the html

{{page$ | async}}

If we have page query parameter then it is rendered on the screen.

Query Parameter

Which actually means that we can inject something directly and then apply to it some code to create local properties.

But now we can do even better. We can move this code to the outside.

const getPageParam = () => {
  return inject(ActivatedRoute).queryParams.pipe(
    filter((params) => params['page']),
    map((params) => params['page'])
  );
};

export class AppComponent implements OnInit {
  page$ = getPageParam()
}

As you can see we moved the whole code to additional function which we call in construction time. It allows us to share such function helpers across different components.

Query Parameter

Our code works just like before but now we fully moved this code outside of our component.

Component inheritance

One more problem that you have really often inside Angular is inheritance. A lot of people like inheritance and when they see classes inside Angular they start to use inheritance a lot. And then they have such problems.

export class Foo {
  constructor(private usersService: UsersService) {}
}

export class Bar extends Foo {
  constructor(usersService: UsersService) {
    super(usersService)
  }
}

The problem here is that if we want to use constructor in Bar we must inject every single dependency of Foo inside and pass them to super. This means that every time when we extend from Foo we must pass and update all dependencies. This is not comfortable.

export class Foo {
  usersService = inject(UsersService);
}

export class Bar extends Foo {}

This inject is it completely different. As we don't use constructor inside Foo we don't need to pass all dependencies even when we need to use constructor in Bar. This cleans our code a lot.

Real example

Here I want to show you a really nice use case of inject function to make something really useful and reusable.

const onDestroy = () => {
  const destroy$ = new Subject<void>();
  const viewRef = inject(ChangeDetectorRef) as ViewRef;

  viewRef.onDestroy(() => {
    destroy$.next();
    destroy$.complete();
  });

  return destroy$;
};

Here I created a function outside of the component. It injects ChangeDetectorRef and completes the Subject when component is destroyed. We need this code to implement unsubscribe logic because to avoid memory leaks we must always unsubscribe from the subscriptions.

Here is how we use it.

export class AppComponent implements OnInit {
  usersService = inject(UsersService);
  destroy$ = onDestroy();

  ngOnInit(): void {
    this.usersService
      .getUsers()
      .pipe(takeUntil(this.destroy$))
      .subscribe(console.log);
  }
}

We call onDestroy in construction time. Now we add takeUntil to each subscription to do unsubscribe when our component is destroyed. Because this code is written outside we don't need to extend anything and our component is DRY.

And actually if you want to learn Angular with NgRx from empty folder to a fully functional production application make sure to check my Angular and NgRx - Building Real Project From Scratch course.

📚 Source code of what we've done