Master Angular services and dependency injection. Learn to create services, understand DI patterns, and manage application state with services.
Master Angular services and dependency injection. Learn to create services, understand DI patterns, and manage application state with services.
Services are classes that contain business logic, data access code, or any functionality that you want
to share across multiple components. They help you:
Separate concerns:
Keep components focused on presentation logic
Reuse code:
Share functionality across multiple components
Organize logic:
Group related functionality together
Test easily:
Services can be tested independently of components
Manage state:
Centralize application state management
Services are TypeScript classes decorated with the
@Injectable()
decorator. Here's a simple example:
1import { Injectable } from '@angular/core';
2
3@Injectable({
4 providedIn: 'root' // Makes this service available application-wide
5})
6export class LoggerService {
7 log(message: string): void {
8 console.log(\`[LOG] \${new Date().toISOString()}: \${message}\`);
9 }
10
11 error(message: string): void {
12 console.error(\`[ERROR] \${new Date().toISOString()}: \${message}\`);
13 }
14
15 warn(message: string): void {
16 console.warn(\`[WARN] \${new Date().toISOString()}: \${message}\`);
17 }
18}Dependency Injection (DI) is a design pattern where a class receives its dependencies from external sources
rather than creating them itself. Angular's DI system provides dependencies to your classes automatically:
Learn about Injecting Services into Components.
1import { Component, inject } from '@angular/core';
2import { LoggerService } from './logger.service';
3
4@Component({
5 selector: 'app-user-list',
6 standalone: true,
7 template: \`
8 <h2>User List</h2>
9 <button (click)="loadUsers()">Load Users</button>
10 \`
11})
12export class UserListComponent {
13 // Modern inject() function (Angular 14+)
14 private logger = inject(LoggerService);
15
16 // Or traditional constructor injection
17 constructor(private loggerService: LoggerService) {}
18
19 loadUsers() {
20 this.logger.log('Loading users...');
21 // Load users logic
22 }
23}Angular provides several ways to configure service providers, each affecting the scope and lifetime
of the service instance:
Learn about 1. Root Provider (Singleton).
1@Injectable({
2 providedIn: 'root' // Single instance for entire application
3})
4export class DataService {
5 // This service will be a singleton
6}Learn about 2. Component-Level Provider.
1@Component({
2 selector: 'app-user-profile',
3 standalone: true,
4 providers: [UserService] // New instance for each component
5})
6export class UserProfileComponent {
7 // Each component instance gets its own UserService instance
8}Learn about 3. Environment Injectors (Standalone).
1import { bootstrapApplication } from '@angular/platform-browser';
2import { provideHttpClient } from '@angular/common/http';
3
4bootstrapApplication(AppComponent, {
5 providers: [
6 provideHttpClient(),
7 ApiService,
8 { provide: API_URL, useValue: 'https://api.example.com' }
9 ]
10});Learn about Real-World Service Examples.
Learn about Data Service.
1import { Injectable, signal } from '@angular/core';
2
3export interface User {
4 id: number;
5 name: string;
6 email: string;
7}
8
9@Injectable({
10 providedIn: 'root'
11})
12export class UserService {
13 // Using signals for reactive state
14 private users = signal<User[]>([]);
15 private loading = signal(false);
16 private error = signal<string | null>(null);
17
18 // Expose read-only signals
19 readonly users$ = this.users.asReadonly();
20 readonly loading$ = this.loading.asReadonly();
21 readonly error$ = this.error.asReadonly();
22
23 async loadUsers(): Promise<void> {
24 this.loading.set(true);
25 this.error.set(null);
26
27 try {
28 const response = await fetch('https://api.example.com/users');
29 const data = await response.json();
30 this.users.set(data);
31 } catch (err) {
32 this.error.set('Failed to load users');
33 } finally {
34 this.loading.set(false);
35 }
36 }
37
38 addUser(user: User): void {
39 this.users.update(current => [...current, user]);
40 }
41
42 removeUser(id: number): void {
43 this.users.update(current => current.filter(u => u.id !== id));
44 }
45
46 getUserById(id: number): User | undefined {
47 return this.users().find(u => u.id === id);
48 }
49}Learn about Authentication Service.
1import { Injectable, signal } from '@angular/core';
2
3export interface AuthUser {
4 id: number;
5 username: string;
6 token: string;
7}
8
9@Injectable({
10 providedIn: 'root'
11})
12export class AuthService {
13 private currentUser = signal<AuthUser | null>(null);
14 private isAuthenticated = signal(false);
15
16 readonly currentUser$ = this.currentUser.asReadonly();
17 readonly isAuthenticated$ = this.isAuthenticated.asReadonly();
18
19 async login(username: string, password: string): Promise<boolean> {
20 try {
21 const response = await fetch('/api/login', {
22 method: 'POST',
23 headers: { 'Content-Type': 'application/json' },
24 body: JSON.stringify({ username, password })
25 });
26
27 if (response.ok) {
28 const user = await response.json();
29 this.currentUser.set(user);
30 this.isAuthenticated.set(true);
31 localStorage.setItem('auth_token', user.token);
32 return true;
33 }
34 return false;
35 } catch (error) {
36 console.error('Login failed:', error);
37 return false;
38 }
39 }
40
41 logout(): void {
42 this.currentUser.set(null);
43 this.isAuthenticated.set(false);
44 localStorage.removeItem('auth_token');
45 }
46
47 async checkAuth(): Promise<boolean> {
48 const token = localStorage.getItem('auth_token');
49 if (!token) return false;
50
51 try {
52 const response = await fetch('/api/verify', {
53 headers: { 'Authorization': \`Bearer \${token}\` }
54 });
55
56 if (response.ok) {
57 const user = await response.json();
58 this.currentUser.set(user);
59 this.isAuthenticated.set(true);
60 return true;
61 }
62 } catch (error) {
63 console.error('Auth check failed:', error);
64 }
65
66 this.logout();
67 return false;
68 }
69}Services can communicate with components and other services using various patterns:
Learn about Event Bus Service.
1import { Injectable } from '@angular/core';
2import { Subject, Observable } from 'rxjs';
3import { filter, map } from 'rxjs/operators';
4
5export interface AppEvent {
6 type: string;
7 payload?: any;
8}
9
10@Injectable({
11 providedIn: 'root'
12})
13export class EventBusService {
14 private eventSubject = new Subject<AppEvent>();
15
16 emit(event: AppEvent): void {
17 this.eventSubject.next(event);
18 }
19
20 on(eventType: string): Observable<any> {
21 return this.eventSubject.pipe(
22 filter(event => event.type === eventType),
23 map(event => event.payload)
24 );
25 }
26}
27
28// Usage:
29// eventBus.emit({ type: 'USER_LOGGED_IN', payload: user });
30// eventBus.on('USER_LOGGED_IN').subscribe(user => {...});Injection tokens allow you to provide non-class dependencies like configuration objects or primitive values:
1import { InjectionToken, inject } from '@angular/core';
2
3// Define injection token
4export const API_CONFIG = new InjectionToken<ApiConfig>('api.config');
5
6export interface ApiConfig {
7 baseUrl: string;
8 timeout: number;
9 retries: number;
10}
11
12// Provide value
13bootstrapApplication(AppComponent, {
14 providers: [
15 {
16 provide: API_CONFIG,
17 useValue: {
18 baseUrl: 'https://api.example.com',
19 timeout: 5000,
20 retries: 3
21 }
22 }
23 ]
24});
25
26// Inject and use
27@Injectable({ providedIn: 'root' })
28export class ApiService {
29 private config = inject(API_CONFIG);
30
31 makeRequest() {
32 console.log('Base URL:', this.config.baseUrl);
33 console.log('Timeout:', this.config.timeout);
34 }
35}Understanding service hierarchies helps you control instance creation and data isolation:
Services provided in 'root' are singletons - one instance for the entire application.
Best for shared state, API clients, and global services.
Services provided in a component's providers array create a new instance for each component.
Best for component-specific state that shouldn't be shared.
providedIn: 'root'
for services that should be singletons
Prefer the modern
function over constructor injection
Keep services focused on a single responsibility
Use signals for reactive state management in services
Make service APIs clear and well-documented
Use injection tokens for configuration and non-class dependencies
Test services independently of components
Avoid circular dependencies between services
Use readonly signals to expose service state safely
Services are classes that encapsulate business logic and data management, promoting code reuse and separation of concerns
Dependency injection automatically provides dependencies to classes, making code more testable and maintainable
The @Injectable() decorator marks a class as available for dependency injection
providedIn: 'root' creates singleton services available throughout the application
The modern inject() function provides a cleaner alternative to constructor injection
Component-level providers create new service instances for each component, enabling isolated state
Signals in services provide reactive state management with automatic change detection
Injection tokens enable providing non-class dependencies like configuration objects
Services should focus on a single responsibility and avoid tight coupling with components
Your progress is saved in this browser, on this device only.