Dependency Injection (DI) is one of Angular's core mechanisms.
Instead of components creating the services they need, Angular creates and manages those services and hands them over. The result: less coupling, easier testing, and code that's simpler to maintain.
- What Is Dependency Injection
- Services and @Injectable
- Injecting a Service
- providedIn Options
- Providing Services in bootstrapApplication
- Component-Level Services
What Is Dependency Injection
Say a component needs a UserService to fetch user data.
Without DI:
export class UserListComponent {
private userService = new UserService(); // created manually
}This works, but it has problems:
- The component is tightly coupled to
UserService - You can't swap it out for a mock in tests
- Each component creates its own instance: no shared state
With DI, Angular handles the creation and provides the instance:
export class UserListComponent {
constructor(private userService: UserService) {} // Angular injects it
}The component just declares what it needs. Angular figures out how to provide it.
Services and @Injectable
A service is a class that handles business logic, data fetching, or shared state.
To make a class injectable, add the @Injectable decorator:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class UserService {
getUsers() {
return [
{ id: 1, name: 'Charmy' },
{ id: 2, name: 'Alice' },
];
}
}@Injectable tells Angular this class can be injected. providedIn: 'root' means the service is a singleton: one instance shared across the entire application.
Injecting a Service
Constructor Injection (traditional)
import { Component } from '@angular/core';
import { UserService } from './user.service';
@Component({
standalone: true,
selector: 'app-user-list',
template: `
- {{ user.name }}
`,
})
export class UserListComponent {
users = [];
constructor(private userService: UserService) {
this.users = this.userService.getUsers();
}
}The inject Function (Angular 14+)
Angular 14 introduced the inject function, which lets you inject dependencies without a constructor:
import { Component, inject } from '@angular/core';
import { UserService } from './user.service';
@Component({
standalone: true,
selector: 'app-user-list',
template: `...`,
})
export class UserListComponent {
private userService = inject(UserService);
users = this.userService.getUsers();
}inject can be used during property initialization, which keeps the code concise. It's the recommended approach in modern standalone Angular.
providedIn Options
The providedIn property on @Injectable determines the scope of the service.
providedIn: 'root'
The most common option. One instance shared across the entire application:
@Injectable({
providedIn: 'root',
})
export class UserService {}Use this for globally shared services: API calls, auth state, user preferences.
providedIn: 'platform'
Shared across multiple Angular applications running on the same page. Rarely needed.
providedIn: 'any'
Each lazy-loaded module gets its own instance. Also rarely used.
Providing Services in bootstrapApplication
In a standalone app, global services and configuration go in the providers array of bootstrapApplication:
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(),
],
});You can also provide custom services or values:
bootstrapApplication(AppComponent, {
providers: [
{ provide: UserService, useClass: UserService },
{ provide: 'API_URL', useValue: 'https://api.example.com' },
],
});To inject a string token, use @Inject:
import { Component, Inject } from '@angular/core';
@Component({ ... })
export class AppComponent {
constructor(@Inject('API_URL') private apiUrl: string) {
console.log(apiUrl); // "https://api.example.com"
}
}Component-Level Services
Services can be scoped to a specific component and its children by declaring them in the component's providers:
import { Component } from '@angular/core';
import { UserService } from './user.service';
@Component({
standalone: true,
selector: 'app-user-section',
providers: [UserService], // separate instance, scoped to this component
template: `...`,
})
export class UserSectionComponent {}This creates a new UserService instance that's isolated from the root-level one. Any child components will share this instance, but nothing outside UserSectionComponent can access it.
Useful when you need isolated state: form state, local data management, or keeping two parts of the UI independent.
Conclusion
Angular DI separates the creation and management of services from the components that use them:
@Injectablemarks a class as injectable- Use constructor injection or the
injectfunction to consume services providedIn: 'root'creates a global singletonbootstrapApplication providershandles app-wide configuration- Component-level
providerscreate isolated instances scoped to that component tree
Once you're comfortable with DI, the natural next topics are:
- Angular service design pattern
- HttpClient and API requests
- Mocking services in tests