Angular Services and Dependency Injection
A service is a class that holds logic or data that several components need, such as a cart or a list of courses.
Why Services?
Components should focus on showing the screen. Work like these belongs in services:
- Loading and saving data from a server
- Sharing data between components, such as a shopping cart
- Common helpers, like formatting or logging
Creating a Service
Example
ng generate service cartThis creates cart.ts (named cart.service.ts in versions before Angular 20). Write the service:
Example
import { Injectable, signal, computed } from "@angular/core";
export interface CartItem {
name: string;
price: number;
}
@Injectable({ providedIn: "root" })
export class CartService {
private items = signal<CartItem[]>([]);
readonly list = this.items.asReadonly();
readonly total = computed(() => this.items().reduce((sum, i) => sum + i.price, 0));
add(item: CartItem) {
this.items.update((list) => [...list, item]);
}
clear() {
this.items.set([]);
}
}@Injectable({ providedIn: "root" }) means Angular creates one shared copy of the service for the whole app.
Using a Service with inject()
Components get services with the inject() function. This is called dependency injection: you ask for what you need, and Angular gives it to you.
Example
import { Component, inject } from "@angular/core";
import { CartService } from "./cart";
@Component({
selector: "app-product",
template: `
<h3>Cricket Bat</h3>
<p>Rs 4,500</p>
<button (click)="cart.add({ name: 'Cricket Bat', price: 4500 })">Add to cart</button>
`,
})
export class Product {
cart = inject(CartService);
}Another component can read the same cart:
Example
@Component({
selector: "app-cart-summary",
template: `<p>{{ cart.list().length }} items, total Rs {{ cart.total() }}</p>`,
})
export class CartSummary {
cart = inject(CartService);
}(after adding the bat twice) 2 items, total Rs 9000
Both components use the same CartService, so they always agree.
You may see services injected through the constructor: constructor(private cart: CartService) {}. It still works. inject() is shorter and the modern style.
Loading Data with HttpClient
Angular's HttpClient talks to servers. First, provide it in app.config.ts:
Example
import { ApplicationConfig } from "@angular/core";
import { provideHttpClient } from "@angular/common/http";
export const appConfig: ApplicationConfig = {
providers: [provideHttpClient()],
};Then use it in a service:
Example
import { Injectable, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs";
export interface User {
id: number;
name: string;
}
@Injectable({ providedIn: "root" })
export class UserService {
private http = inject(HttpClient);
getUsers(): Observable<User[]> {
return this.http.get<User[]>("https://jsonplaceholder.typicode.com/users");
}
}And show the result in a component. toSignal turns the HTTP result into a signal:
Example
import { Component, inject } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { UserService } from "./user";
@Component({
selector: "app-users",
template: `
@for (u of users(); track u.id) {
<p>{{ u.name }}</p>
} @empty {
<p>Loading...</p>
}
`,
})
export class Users {
users = toSignal(inject(UserService).getUsers(), { initialValue: [] });
}HttpClient returns an Observable from the RxJS library. You will learn more about RxJS as you build larger apps.
Summary
| Idea | Code |
|---|---|
| Make a class injectable | @Injectable({ providedIn: "root" }) |
| Get a service | inject(CartService) |
| Enable HTTP | provideHttpClient() in app.config.ts |
| GET request | this.http.get<T>(url) |
Exercise
Exercise
What does providedIn: "root" mean for a service?
