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.
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.
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 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.
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.
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.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done