Dependency Injection (DI) is the architectural pattern that powers Angular. It's how components receive the tools they need to function without knowing how those tools are created.
1The Singleton Pattern
In Angular, most services are singletons. When you provide a service in the 'root', Angular creates one single instance of that class the first time it is requested. Every subsequent component that asks for that service receives a reference to that same exact instance. This is incredibly powerful for state management: if you store a user's profile in a service, every component in your app can access and update that same data in real-time.
2Why DI Matters
Without DI, your components would be 'tightly coupled' to their dependencies. If a component creates its own ApiService using new, you can't easily swap that service out for a 'Mock' version during testing. With DI, the component simply says 'I need something that looks like an ApiService', and Angular provides it. This makes your code modular, flexible, and extremely easy to test.
3Step-by-Step Breakdown
Components should only handle the UI. For business logic, data fetching, and state sharing, we use Services. This is the 'Separation of Concerns' principle.
Think of a service as a specialized tool. Instead of every component building its own hammer, they all share one hammer from a central toolbox.
But how does a component get the service? We don't use 'new DataService()'. We use Dependency Injection (DI).
Checkpoint: In Angular, what is the preferred way to get an instance of a service in a component?
- โUsing the 'new' keyword
- โUsing Dependency Injection in the constructor
The Angular Injector is like a waiter. You ask for a service in your constructor, and the Injector brings it to you.
By default, services in Angular are Singletons. This means there's only ONE instance of the service shared across the entire app.
Checkpoint: If Component A changes a value in a Singleton service, will Component B see that change?
- โYes, because they share the same instance
- โNo, each component gets its own instance
This makes services perfect for sharing data between components that aren't directly related. No more complex @Input/@Output chains!
Ready to build your first service? Let's look at the @Injectable decorator next.
Level Up ๐
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Moving Logic Into Services Keeps Components Focused on Accessible Template Structure
When data-fetching, formatting, and business logic live in an injected service rather than the component class, the component itself becomes easier to reason about purely in terms of its template โ the part that actually needs accessibility attention.
2Dependency Injection Itself Has No Accessibility Implications โ It's a Pure Code-Organization Pattern
Whether a component gets its data from an injected service or fetches it inline makes no difference to what a screen reader perceives; accessibility is determined entirely by the resulting template markup, not by how the underlying logic is wired together.
SEO Implications
- 1
DI Has No Direct SEO Effect โ It's an Internal Application Architecture Concern
How a component obtains its dependencies (constructor injection, service scope) is invisible to search engines; what matters for SEO is only whether the data those services fetch ends up rendered in server-side HTML.
- 2
Well-Organized Services Make Angular Universal (SSR) Setup Easier to Reason About
Cleanly separated services (each with a clear, single responsibility) are much easier to audit for SSR-safety โ checking each one individually for browser-only API usage โ than business logic tangled directly inside component classes.
Best Practices
Inject Dependencies via the Constructor, Not by Manually Instantiating Classes
`constructor(private userService: UserService)` lets Angular's DI system manage the instance's lifecycle and scope automatically โ manually writing `new UserService()` inside a component bypasses DI entirely, losing singleton behavior, testability via mocking, and consistent scoping.
Depend on Abstractions (Interfaces) in Complex Apps Where Multiple Implementations May Exist
For services likely to have swappable implementations (like a payment provider), injecting against an interface/injection token rather than a concrete class makes swapping implementations (or mocking in tests) far simpler.
Frequent Bugs
A component's constructor works fine at runtime but unit tests fail with a 'No provider for X' error.
The test's TestBed configuration doesn't include the service in its providers array (or a mock/stub for it) โ unlike the running app, where the service is available via the module tree, an isolated test environment needs its dependencies explicitly configured in the testing module.
Manually instantiating a service with `new` inside a component causes unexpected behavior compared to injecting it normally.
A manually created instance (`new MyService()`) is a completely separate object from whatever instance Angular's DI system manages and injects elsewhere โ any shared state the service is supposed to maintain won't be shared with this manual instance. Always obtain services through constructor injection so you get the correctly scoped, shared instance.
Real-World Examples
Component Delegating Logic to an Injected Service
A component stays focused purely on template and presentation logic, delegating all data-fetching and business logic to an injected service obtained through the constructor.
@Component({ selector: 'app-user-list', templateUrl: './user-list.component.html' })
export class UserListComponent {
users$ = this.userService.getUsers();
constructor(private userService: UserService) {}
}