Angular’s hierarchical injector tree means every component has its own injector that can provide services scoped to that component and its descendants. When a service is requested, Angular walks up the injector tree until it finds a provider. providedIn: 'root' services are at the top. Services in component providers: [] are scoped to that component subtree. Understanding this hierarchy lets you provide the same service class with independent instances to different parts of the component tree — essential for paginated lists, multi-step forms, and repeated UI patterns.
Component-Level Providers
// ── PaginationService — stateful, should be per-component ─────────────────
@Injectable() // NO providedIn — must be explicitly provided
export class PaginationService {
private _page = signal(1);
private _size = signal(10);
private _total = signal(0);
readonly page = this._page.asReadonly();
readonly size = this._size.asReadonly();
readonly total = this._total.asReadonly();
readonly pages = computed(() => Math.ceil(this._total() / this._size()));
readonly hasNext = computed(() => this._page() < this.pages());
readonly hasPrev = computed(() => this._page() > 1);
setPage(page: number): void { this._page.set(page); }
setTotal(total: number): void { this._total.set(total); }
next(): void { if (this.hasNext()) this._page.update(p => p + 1); }
prev(): void { if (this.hasPrev()) this._page.update(p => p - 1); }
reset(): void { this._page.set(1); }
}
// ── Component providing its own PaginationService instance ────────────────
@Component({
selector: 'app-post-list',
standalone: true,
providers: [PaginationService], // ← scoped to this component + descendants
imports: [PaginationControlsComponent],
template: `
<!-- PaginationControlsComponent also injects PaginationService ──────── -->
<!-- It gets the SAME instance as PostListComponent (same subtree) ─────── -->
<app-pagination-controls />
<div class="posts">...</div>
`,
})
export class PostListComponent implements OnInit {
// Gets the scoped instance (not the root singleton)
protected pagination = inject(PaginationService);
private posts$ = inject(PostsService);
ngOnInit() {
// When pagination.page() changes, load new page
effect(() => {
this.posts$.loadPublished(this.pagination.page());
});
}
}
// ── Two PostListComponents on the same page → two independent paginators ──
// <app-post-list /> ← has its own PaginationService instance (page 1–10)
// <app-post-list /> ← has its own PaginationService instance (page 1–5)
// These are completely independent — navigating page 3 on one does not
// affect the other's page counter
// ── viewProviders — exclude projected content from provider scope ──────────
@Component({
selector: 'app-form-wrapper',
standalone: true,
// viewProviders: only visible to this component's own template children
// (not ng-content projected children)
viewProviders: [FormStateService],
template: `
<form>
<app-title-input /> <!-- Can inject FormStateService ── -->
<ng-content /> <!-- Projected content CANNOT inject FormStateService -->
</form>
`,
})
providers: [] array, that service is created when the component is created and destroyed when the component is destroyed. This lifecycle binding is what makes component-level providers ideal for stateful services (form state, pagination, wizard steps) — the state is automatically cleaned up when the user navigates away and the component is destroyed. Root services accumulate state indefinitely; component-level services are naturally scoped.providedIn from services that should only be used at the component level: @Injectable() without any configuration. This prevents the service from being tree-shaken by the root injector and makes the intention clear — this service must be explicitly provided where it is used. If it is accidentally injected without a provider in the hierarchy, Angular throws a helpful “No provider for X” error rather than silently injecting the root singleton.providers: [] and viewProviders: [] on a component is subtle but important. providers makes the service available to the component, its view children, and its projected content (ng-content). viewProviders makes it available only to the component’s own view children — projected content cannot inject it. Use viewProviders when the service should be private to the component’s own template and not accessible to content inserted from outside.Common Mistakes
Mistake 1 — Providing a stateful service at root when it should be per-component (shared state when it should be independent)
❌ Wrong — @Injectable({ providedIn: 'root' }) on PaginationService; all paginated lists share one page counter.
✅ Correct — @Injectable() with providers: [PaginationService] on each component that needs its own state.
Mistake 2 — Using component providers for services that need to be shared across sibling components
❌ Wrong — AuthService in component providers; each component gets its own auth state; login in one component doesn’t affect siblings.
✅ Correct — shared state belongs at root; component-scoped state belongs in component providers.