S

Services & DI

Type-safe Angular services with dependency injection, HTTP client, and RxJS observables.

Code

typescript
1// user.service.ts
2import { Injectable, inject } from "@angular/core";
3import { HttpClient, HttpParams, HttpErrorResponse } from "@angular/common/http";
4import { Observable, throwError, BehaviorSubject } from "rxjs";
5import { map, catchError, tap, retry } from "rxjs/operators";
6
7// Types
8interface User {
9 id: number;
10 name: string;
11 email: string;
12 role: "admin" | "user";
13}
14
15interface CreateUserDto {
16 name: string;
17 email: string;
18 role?: "admin" | "user";
19}
20
21interface UpdateUserDto extends Partial<CreateUserDto> {}
22
23interface PaginatedResponse<T> {
24 data: T[];
25 total: number;
26 page: number;
27 limit: number;
28}
29
30interface QueryParams {
31 page?: number;
32 limit?: number;
33 search?: string;
34 role?: "admin" | "user";
35}
36
37@Injectable({
38 providedIn: "root",
39})
40export class UserService {
41 private readonly http = inject(HttpClient);
42 private readonly apiUrl = "/api/users";
43
44 // State management with BehaviorSubject
45 private usersSubject = new BehaviorSubject<User[]>([]);
46 public users$ = this.usersSubject.asObservable();
47
48 private loadingSubject = new BehaviorSubject<boolean>(false);
49 public loading$ = this.loadingSubject.asObservable();
50
51 // GET all users with pagination
52 getUsers(params: QueryParams = {}): Observable<PaginatedResponse<User>> {
53 let httpParams = new HttpParams();
54
55 if (params.page) httpParams = httpParams.set("page", params.page.toString());
56 if (params.limit) httpParams = httpParams.set("limit", params.limit.toString());
57 if (params.search) httpParams = httpParams.set("search", params.search);
58 if (params.role) httpParams = httpParams.set("role", params.role);
59
60 this.loadingSubject.next(true);
61
62 return this.http
63 .get<PaginatedResponse<User>>(this.apiUrl, { params: httpParams })
64 .pipe(
65 tap((response) => {
66 this.usersSubject.next(response.data);
67 this.loadingSubject.next(false);
68 }),
69 catchError(this.handleError.bind(this))
70 );
71 }
72
73 // GET single user
74 getUser(id: number): Observable<User> {
75 return this.http.get<User>(`${this.apiUrl}/${id}`).pipe(
76 retry(2),
77 catchError(this.handleError.bind(this))
78 );
79 }
80
81 // POST create user
82 createUser(data: CreateUserDto): Observable<User> {
83 return this.http.post<User>(this.apiUrl, data).pipe(
84 tap((newUser) => {
85 const currentUsers = this.usersSubject.value;
86 this.usersSubject.next([...currentUsers, newUser]);
87 }),
88 catchError(this.handleError.bind(this))
89 );
90 }
91
92 // PATCH update user
93 updateUser(id: number, data: UpdateUserDto): Observable<User> {
94 return this.http.patch<User>(`${this.apiUrl}/${id}`, data).pipe(
95 tap((updatedUser) => {
96 const currentUsers = this.usersSubject.value;
97 const index = currentUsers.findIndex((u) => u.id === id);
98 if (index !== -1) {
99 currentUsers[index] = updatedUser;
100 this.usersSubject.next([...currentUsers]);
101 }
102 }),
103 catchError(this.handleError.bind(this))
104 );
105 }
106
107 // DELETE user
108 deleteUser(id: number): Observable<void> {
109 return this.http.delete<void>(`${this.apiUrl}/${id}`).pipe(
110 tap(() => {
111 const currentUsers = this.usersSubject.value;
112 this.usersSubject.next(currentUsers.filter((u) => u.id !== id));
113 }),
114 catchError(this.handleError.bind(this))
115 );
116 }
117
118 // Error handler
119 private handleError(error: HttpErrorResponse): Observable<never> {
120 this.loadingSubject.next(false);
121
122 let errorMessage = "An error occurred";
123
124 if (error.error instanceof ErrorEvent) {
125 // Client-side error
126 errorMessage = error.error.message;
127 } else {
128 // Server-side error
129 errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
130 }
131
132 console.error(errorMessage);
133 return throwError(() => new Error(errorMessage));
134 }
135}
136
137// auth.service.ts - Authentication service
138@Injectable({
139 providedIn: "root",
140})
141export class AuthService {
142 private readonly http = inject(HttpClient);
143 private currentUserSubject = new BehaviorSubject<User | null>(null);
144 public currentUser$ = this.currentUserSubject.asObservable();
145
146 public get currentUserValue(): User | null {
147 return this.currentUserSubject.value;
148 }
149
150 public get isAuthenticated(): boolean {
151 return this.currentUserValue !== null;
152 }
153
154 login(email: string, password: string): Observable<{ user: User; token: string }> {
155 return this.http
156 .post<{ user: User; token: string }>("/api/auth/login", { email, password })
157 .pipe(
158 tap((response) => {
159 localStorage.setItem("token", response.token);
160 this.currentUserSubject.next(response.user);
161 })
162 );
163 }
164
165 logout(): void {
166 localStorage.removeItem("token");
167 this.currentUserSubject.next(null);
168 }
169}

Run this example locally

$ ng generate service user