Dependency injection is the mechanism Angular uses to supply a component with the services it needs, rather than the component constructing them itself — a service is just a plain class marked injectable, and where it's "provided" determines how many instances of it actually exist.
Generating a service with the CLI
ng generate service product
A basic injectable service
@Injectable({
providedIn: 'root'
})
export class ProductService {
constructor(private http: HttpClient) {}
getProducts(): Observable {
return this.http.get('/api/products');
}
}
Injecting the service into a component
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html'
})
export class ProductListComponent implements OnInit {
products: Product[] = [];
constructor(private productService: ProductService) {}
ngOnInit(): void {
this.productService.getProducts().subscribe(products => {
this.products = products;
});
}
}
Declaring private productService: ProductService as a constructor parameter is all that's needed — Angular's dependency injector automatically resolves and supplies the correct instance, without the component ever calling new ProductService() directly.
Why providedIn: 'root' matters
@Injectable({
providedIn: 'root'
})
providedIn: 'root' registers the service with the application's root injector, making it a genuine singleton shared across the entire app — every component that injects ProductService gets the exact same instance, which matters for a service holding shared state (like a shopping cart) that multiple components need to see consistently.
Scoping a service to a specific module instead
@NgModule({
providers: [ProductService]
})
export class AdminModule {}
Listing a service in a specific module's own providers array (rather than using providedIn: 'root') creates a separate instance scoped to that module — genuinely useful when different parts of the app should each get their own independent instance rather than sharing global state.
Scoping a service to a single component
@Component({
selector: 'app-shopping-cart',
providers: [CartService]
})
export class ShoppingCartComponent {}
Listing a service in a component's own providers array creates a new instance specifically for that component (and its children) — this instance is destroyed when the component is destroyed, useful for state that should genuinely reset each time the component is created fresh, rather than persisting for the app's whole lifetime.
Injecting one service into another
@Injectable({
providedIn: 'root'
})
export class OrderService {
constructor(private cartService: CartService, private http: HttpClient) {}
placeOrder(): Observable {
const items = this.cartService.getItems();
return this.http.post('/api/orders', { items });
}
}
Services can depend on other services through the exact same constructor-injection mechanism used for injecting a service into a component — Angular's injector resolves the full dependency graph automatically, regardless of how deep the chain goes.
Using the inject() function as an alternative to constructor injection
export class ProductListComponent {
private productService = inject(ProductService);
}
The standalone inject() function, introduced as a modern alternative to constructor parameter injection, achieves the same result — genuinely useful in contexts like route guards or standalone functions where a class constructor isn't available to receive injected parameters.