Angular Dependency Injection & Services
Dependency Injection (DI) is a core concept in Angular. It allows you to inject dependencies into components and services rather than creating them manually. Services are singleton objects that encapsulate business logic and data.
Creating Services
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
// Service with providedIn: 'root' (singleton)
@Injectable({
providedIn: 'root' // Available app-wide
})
export class UserService {
private apiUrl = 'https://api.example.com/users';
private usersSubject = new BehaviorSubject<User[]>([]);
public users$ = this.usersSubject.asObservable();
constructor(private http: HttpClient) {
this.loadUsers();
}
private loadUsers() {
this.http.get<User[]>(this.apiUrl).subscribe(
users => this.usersSubject.next(users)
);
}
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
getUser(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
createUser(user: User): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}
updateUser(id: number, user: User): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${id}`, user);
}
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}
interface User {
id: number;
name: string;
email: string;
}Injecting Services
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-user-list',
template: `
<div>
<h2>Users</h2>
<ul>
<li *ngFor="let user of users">
{{ user.name }} - {{ user.email }}
</li>
</ul>
</div>
`
})
export class UserListComponent implements OnInit {
users: User[] = [];
// Constructor injection (traditional)
constructor(private userService: UserService) { }
ngOnInit() {
this.userService.getUsers().subscribe(
users => this.users = users
);
}
}
// Using inject() function (Angular 14+)
import { inject } from '@angular/core';
@Component({
selector: 'app-user-detail',
template: `<div>{{ user?.name }}</div>`
})
export class UserDetailComponent implements OnInit {
// Modern inject() function
private userService = inject(UserService);
private route = inject(ActivatedRoute);
user: User | null = null;
ngOnInit() {
const id = Number(this.route.snapshot.paramMap.get('id'));
this.userService.getUser(id).subscribe(
user => this.user = user
);
}
}Provider Scope
// 1. Root level - singleton across entire app
@Injectable({
providedIn: 'root'
})
export class GlobalService { }
// 2. Module level - singleton within module
@NgModule({
providers: [ModuleScopedService]
})
export class FeatureModule { }
// 3. Component level - new instance per component
@Component({
selector: 'app-example',
providers: [ComponentScopedService] // New instance
})
export class ExampleComponent { }
// 4. Lazy loaded module - singleton within lazy module
@Injectable({
providedIn: 'any' // New instance per lazy module
})
export class LazyService { }Injection Tokens
InjectionTokens allow you to inject values that are not classes, like configuration objects.
import { InjectionToken } from '@angular/core';
// Define configuration interface
export interface AppConfig {
apiUrl: string;
production: boolean;
version: string;
}
// Create injection token
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
// Provide value in module
@NgModule({
providers: [
{
provide: APP_CONFIG,
useValue: {
apiUrl: 'https://api.example.com',
production: false,
version: '1.0.0'
}
}
]
})
export class AppModule { }
// Inject in component or service
@Injectable()
export class ApiService {
constructor(@Inject(APP_CONFIG) private config: AppConfig) {
console.log('API URL:', this.config.apiUrl);
}
}Hierarchical Injectors
// Parent component with service
@Component({
selector: 'app-parent',
providers: [SharedService], // Parent instance
template: `
<app-child-a></app-child-a>
<app-child-b></app-child-b>
`
})
export class ParentComponent {
constructor(private shared: SharedService) {
this.shared.setValue('from parent');
}
}
// Child A - inherits parent's service instance
@Component({
selector: 'app-child-a',
template: `<p>{{ shared.getValue() }}</p>`
})
export class ChildAComponent {
constructor(public shared: SharedService) { }
}
// Child B - has own instance
@Component({
selector: 'app-child-b',
providers: [SharedService], // Own instance
template: `<p>{{ shared.getValue() }}</p>`
})
export class ChildBComponent {
constructor(public shared: SharedService) {
this.shared.setValue('child B');
}
}Optional and Self Decorators
import { Component, Optional, Self, SkipSelf, Host } from '@angular/core';
@Component({
selector: 'app-example'
})
export class ExampleComponent {
constructor(
// Optional: Don't throw error if not found
@Optional() private optionalService: OptionalService | null,
// Self: Only look in current component
@Self() private selfService: SelfService,
// SkipSelf: Skip current component, look up hierarchy
@SkipSelf() private parentService: ParentService,
// Host: Look up to host component only
@Host() private hostService: HostService
) {
if (optionalService) {
optionalService.doSomething();
}
}
}Service Communication Pattern
import { Injectable } from '@angular/core';
import { BehaviorSubject, Subject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class StateService {
// BehaviorSubject - has initial value, replays last value
private dataSubject = new BehaviorSubject<any[]>([]);
public data$ = this.dataSubject.asObservable();
// Subject - no initial value, no replay
private eventSubject = new Subject<string>();
public events$ = this.eventSubject.asObservable();
// Update data
updateData(data: any[]) {
this.dataSubject.next(data);
}
// Get current value
getCurrentData(): any[] {
return this.dataSubject.value;
}
// Emit event
emitEvent(event: string) {
this.eventSubject.next(event);
}
}
// Component A - produces data
@Component({ /* ... */ })
export class ProducerComponent {
constructor(private state: StateService) { }
updateState() {
this.state.updateData([1, 2, 3]);
this.state.emitEvent('data-updated');
}
}
// Component B - consumes data
@Component({ /* ... */ })
export class ConsumerComponent implements OnInit {
data: any[] = [];
constructor(private state: StateService) { }
ngOnInit() {
this.state.data$.subscribe(data => {
this.data = data;
});
this.state.events$.subscribe(event => {
console.log('Event:', event);
});
}
}Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free