Understanding Dependency Injection in Angular
Dependency injection is another concept that is not unique to Angular, it is a general programming pattern. In the context of Angular, the idea is that instead of creating instances of classes ourselves, e.g:
export class AppComponent {
myService = new MyService();
}
Angular will handle creating new
instances of our classes, and provide them to
us through dependency injection which looks like this:
export class AppComponent {
constructor(private myService: MyService){}
}
or this:
export class AppComponent {
private myService = inject(MyService);
}
Angular will use the MyService
type here as an InjectionToken
. This is how
Angular knows we want an instance of MyService
. The instance of MyService
is
assigned to myService
and then we can use it. By using the private
or
public
keyword, this will make myService
available as a class member to the
entire class, rather than just being available within the constructor
.
Technically, the private
keyword is only required for the inject
syntax if
you want the class member to specifically be private
.
NOTE: Using the private
keyword means it will only be available within the
class. If you need it to also be available to the template for this class you
can use the public
keyword.
In both of the examples above, we now have an instance of MyService
that is
available on this.myService
throughout the class.
Why Dependency Injection?
Why do this in Angular? Why not just create new instances ourselves? Some benefits are:
- It makes automated testing easier as we can easily provide fake versions of dependencies instead of real ones (automated testing is a bit more advanced and not something we actually cover in this course)
- It allows us to share instances of our objects more easily. This way we can have two separate components share the same single instance of a service, which means they will be able to share data, or we could create two separate instances of the service if we prefer.
- It makes creating instances much easier, as we don’t need to pass all of the dependencies that the service itself depends on.
That last one is a bit less clear, so let’s expand on that. Imagine that
MyService
also has two dependencies of its own that it needs injected:
export class MyService {
constructor(http: HttpClient, someOtherService: SomeOtherService){}
}
To do its job, MyService
also needs an instance of HttpClient
and
SomeOtherService
. If we are using Angular’s dependency injection system
then we can just do this to inject MyService
somewhere else in our app:
export class AppComponent {
constructor(private myService: MyService){}
}
But if we are not using dependency injection, then we need to pass all of
the MyService
dependencies manually like this:
export class AppComponent {
myService = new MyService(new HttpClient(), new SomeOtherService())
}
Imagine doing this several times throughout your application, and then we need
to update MyService
to have a third dependency:
export class MyService {
constructor(http: HttpClient, someOtherService: SomeOtherService, anotherService: AnotherService){}
}
Now we would need to go back and update every instance of MyService
we are
creating:
export class AppComponent {
myService = new MyService(new HttpClient(), new SomeOtherService(), new AnotherService())
}
Perhaps you can see how this would become a massive pain.
Understanding Tokens and Providers
We have already seen how dependency injection works: