LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2418 min read

Services & Dependency Injection

Share logic across components using injectable services and Angular's built-in dependency injection system.

Why Services Exist

A service is a plain TypeScript class dedicated to logic that doesn't belong to any one component — fetching data, managing shared state, or wrapping a browser API. Components stay focused on presenting UI; services hold the reusable logic behind it.

Creating a Service

The Angular CLI generates a service exactly like a component, and the @Injectable decorator marks the class as something Angular's dependency injection system can provide.

ng generate service posts
# shorthand: ng g s posts
// posts.service.ts
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class PostsService {
private posts = [{ id: 1, title: 'Hello Angular' }];
getPosts() {
return this.posts;
}
}
providedIn: 'root'

This tells Angular to create exactly one shared instance of the service for the whole application (a singleton), and to make it available anywhere without needing to list it in a providers array manually.

Injecting a Service via the Constructor

The classic way to receive a service is a typed constructor parameter — Angular's DI system automatically supplies the right instance.

import { Component } from '@angular/core';
import { PostsService } from './posts.service';
@Component({ selector: 'app-posts', standalone: true, template: `...` })
export class PostsComponent {
posts;
constructor(private postsService: PostsService) {
this.posts = this.postsService.getPosts();
}
}

The Modern inject() Function

Modern Angular offers inject() as an alternative, letting you retrieve a dependency as a plain function call — useful outside a constructor (like inside a field initializer or a route guard function).

import { Component, inject } from '@angular/core';
import { PostsService } from './posts.service';
@Component({ selector: 'app-posts', standalone: true, template: `...` })
export class PostsComponent {
private postsService = inject(PostsService);
posts = this.postsService.getPosts();
}

How Dependency Injection Works

Rather than a component creating its own instance of a service directly (new PostsService()), it declares that it needs one, and Angular's injector supplies it — resolving the exact right instance based on where the service is provided (root, a specific component, or a route). This makes services easy to swap out, mock in tests, and share consistently across the app.

Component declares a dependency (constructor param or inject())
↓
Angular's injector looks up the matching provider
↓
The injector supplies (or creates) the instance
↓
The component uses it, unaware of how it was constructed

Common Beginner Mistakes

Instantiating a service directly with new

This bypasses Angular's DI entirely, creating a separate instance that won't share state with the rest of the app, and can't be mocked in tests the standard way — always inject services, never construct them manually.

Forgetting @Injectable() on a service class

Without it, Angular has no metadata to know how to construct or provide the class — this decorator is required even for a service with no dependencies of its own.

FAQs

Not entirely — both remain fully supported; inject() is often preferred in modern code for its flexibility outside constructors, but constructor injection is still common and equally valid.

Yes — services can depend on other services exactly like components do, and Angular resolves the whole dependency graph automatically.

Summary

Services hold reusable logic outside components, and Angular's dependency injection system supplies the right instance automatically, whether via a constructor or inject(). Next, you'll go deeper into how providers and injection tokens control that resolution.

Next Lesson →

Providers & Injection Tokens