Angular Services & Dependency Injection
Services are a fundamental part of Angular applications that help organize and share code across components. Angular's dependency injection system makes services available to components that need them.
What are Services?
Services in Angular are:
- Singleton objects that get instantiated only once during the lifetime of an application
- Used to organize and share business logic, data, or functions across components
- Injectable into components, directives, pipes, and other services
Creating a Service
You can create a service using the Angular CLI:
ng generate service service-name
Or manually by creating a TypeScript class with the @Injectable
decorator:
// data.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class DataService {
constructor() { }
}
Basic Service Example
// logger.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class LoggerService {
log(message: string) {
console.log(`LOG: ${message}`);
}
error(message: string) {
console.error(`ERROR: ${message}`);
}
}
Using a Service in a Component
To use a service in a component, you need to:
- Import the service
- Add it to the component's constructor
- Use its methods/properties
// app.component.ts
import { Component } from '@angular/core';
import { LoggerService } from './logger.service';
@Component({
selector: 'app-root',
template: `<button (click)="logMessage()">Log Message</button>`
})
export class AppComponent {
constructor(private logger: LoggerService) {}
logMessage() {
this.logger.log('Button clicked!');
}
}
Dependency Injection (DI)
Angular's DI system:
- Creates and manages service instances
- Provides services to components that declare them as dependencies
- Maintains a hierarchical injector system
How DI Works
- You register a provider for a service (usually with
providedIn: 'root'
) - Angular creates a single, shared instance of the service
- When a component requests the service in its constructor, Angular provides the instance
Service Providers
Services can be provided at different levels:
- Root level: Single instance shared across the entire app
- Module level: Single instance shared across the module
- Component level: New instance for each component instance
Root Level Provider
@Injectable({
providedIn: 'root'
})
export class MyService { }
Module Level Provider
// my.module.ts
@NgModule({
providers: [MyService]
})
export class MyModule { }
Component Level Provider
@Component({
selector: 'app-my',
templateUrl: './my.component.html',
providers: [MyService]
})
export class MyComponent { }
Sharing Data Between Components
Services are commonly used to share data between components:
// data.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
private messageSource = new BehaviorSubject<string>('default message');
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message);
}
}
// sender.component.ts
import { Component } from '@angular/core';
import { DataService } from '../data.service';
@Component({
selector: 'app-sender',
template: `
<input [(ngModel)]="message">
<button (click)="sendMessage()">Send</button>
`
})
export class SenderComponent {
message: string;
constructor(private data: DataService) { }
sendMessage() {
this.data.changeMessage(this.message);
}
}
// receiver.component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';
@Component({
selector: 'app-receiver',
template: `<p>{{message}}</p>`
})
export class ReceiverComponent implements OnInit {
message: string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message);
}
}
HTTP Service Example
A common use case for services is making HTTP requests:
// user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'https://api.example.com/users';
constructor(private http: HttpClient) { }
getUsers(): Observable<any[]> {
return this.http.get<any[]>(this.apiUrl);
}
getUser(id: number): Observable<any> {
return this.http.get<any>(`${this.apiUrl}/${id}`);
}
createUser(user: any): Observable<any> {
return this.http.post<any>(this.apiUrl, user);
}
}
// user.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-user',
template: `
<ul>
<li *ngFor="let user of users">{{user.name}}</li>
</ul>
`
})
export class UserComponent implements OnInit {
users: any[];
constructor(private userService: UserService) { }
ngOnInit() {
this.userService.getUsers().subscribe(
users => this.users = users,
error => console.error('Error fetching users', error)
);
}
}
Service Best Practices
- Keep services focused on a single purpose
- Use
providedIn: 'root'
for singleton services - Move business logic from components to services
- Use RxJS Observables for asynchronous operations
- Handle errors in services, not components
Advanced Dependency Injection
Angular's DI system supports more advanced scenarios:
Injection Tokens
For injecting values that aren't classes (like configuration objects):
// app.config.ts
import { InjectionToken } from '@angular/core';
export const APP_CONFIG = new InjectionToken<any>('app.config');
export const AppConfig = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
// app.module.ts
import { APP_CONFIG, AppConfig } from './app.config';
@NgModule({
providers: [
{ provide: APP_CONFIG, useValue: AppConfig }
]
})
export class AppModule { }
// some.service.ts
import { Inject, Injectable } from '@angular/core';
import { APP_CONFIG } from './app.config';
@Injectable()
export class SomeService {
constructor(@Inject(APP_CONFIG) private config: any) {
console.log(config.apiUrl);
}
}
Factory Providers
For creating services that need special initialization:
// app.module.ts
@NgModule({
providers: [
{
provide: MyService,
useFactory: (http: HttpClient) => {
return new MyService(http, 'https://api.example.com');
},
deps: [HttpClient]
}
]
})
export class AppModule { }
Next: Routing & Navigation