ngx-admin là một template quản trị được xây dựng trên Angular 8+ và thư viện Nebular, tích hợp sẵn Bootstrap 4 cùng Angular Material để cung cấp các thành phần giao diện phong phú. Dự án giúp tăng tốc phát triển ứng dụng doanh nghiệp nhờ kiến trúc rõ ràng và hệ thống dịch vụ được thiết kế tốt. Một trong những nền tảng quan trọng nhất chính là cơ chế dịch vụ đơn thể (singleton service) và Dependency Injection (DI).
Dịch vụ đơn thể trong Angular là gì?
Trong Angular, dịch vụ là một lớp có thể được tiêm vào (injectable) nhằm thực hiện logic nghiệp vụ hoặc truy xuất dữ liệu. Khi được khai báo dưới dạng đơn thể, dịch vụ chỉ được khởi tạo duy nhất một lần trong suốt vòng đời ứng dụng. Điều này đảm bảo tính nhất quán của trạng thái và tối ưu hiệu năng.
Các cách triển khai dịch vụ trong ngx-admin
Trong mã nguồn ngx-admin, các dịch vụ thường nằm trong các thư mục như src/app/@core/utils/ hoặc src/app/@core/mock/. Có hai cách phổ biến để đăng ký dịch vụ:
1. Đăng ký ở cấp module
Ví dụ trong file earning.service.ts:
@Injectable()
export class EarningService extends EarningData {
// Triển khai logic
}
Dịch vụ này phải được liệt kê trong mảng providers của một module cụ thể, chẳng hạn:
@NgModule({
providers: [EarningService]
})
export class MockDataModule {}
2. Đăng ký ở cấp root (tạo dịch vụ đơn thể)
Cách hiện đại hơn là sử dụng thuộc tính providedIn: 'root' ngay trong decorator @Injectable:
@Injectable({
providedIn: 'root'
})
export class AnalyticsService {
// Triển khai logic
}
Khi đó, Angular tự động đăng ký dịch vụ với injector gốc, đảm bảo chỉ tồn tại một thể hiện duy nhất trên toàn ứng dụng.
Tạo và sử dụng dịch vụ đơn thể
Bước 1: Tạo file mới, ví dụ shared-data.service.ts trong thư mục utils.
Bước 2: Định nghĩa dịch vụ với providedIn: 'root':
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class SharedDataService {
private items: string[] = [];
addItem(value: string): void {
this.items.push(value);
}
getItems(): string[] {
return [...this.items];
}
}
Bước 3: Tiêm dịch vụ vào component mà không cần đăng ký thủ công:
import { Component } from '@angular/core';
import { SharedDataService } from '../../@core/utils/shared-data.service';
@Component({
selector: 'app-dashboard',
template: '<p>{{ itemCount }} mục đã lưu</p>'
})
export class DashboardComponent {
itemCount = 0;
constructor(private dataService: SharedDataService) {
this.dataService.addItem('Dữ liệu mẫu');
this.itemCount = this.dataService.getItems().length;
}
}
Lợi ích và nguyên tắc khi làm việc với DI
Lợi ích chính:
- Tách biệt logic: Thành phần giao diện không phụ thuộc trực tiếp vào logic nghiệp vụ.
- Tái sử dụng: Cùng một dịch vụ có thể dùng ở nhiều nơi.
- Dễ kiểm thử: Có thể mock dễ dàng trong unit test.
Nguyên tắc tốt:
- Ưu tiên
providedIn: 'root'nếu dịch vụ dùng chung toàn ứng dụng. - Định nghĩa interface cho các contract trừu tượng (xem
src/app/@core/data/). - Không lạm dụng lưu trữ trạng thái toàn cục trong dịch vụ — trừ khi thật sự cần thiết.
- Giữ mỗi dịch vụ tập trung vào một nhiệm vụ duy nhất.
Một số tình huống thường gặp
Hỏi: Làm sao để truyền dữ liệu giữa các dịch vụ?
Đáp: Có thể tiêm một dịch vụ vào dịch vụ khác, hoặc sử dụng giải pháp quản lý trạng thái như NgRx hoặc RxJS BehaviorSubject.
Hỏi: Khi nào nên dùng providers trong module thay vì providedIn: 'root'?
Đáp: Khi dịch vụ chỉ phục vụ cho một tính năng/module riêng lẻ, hoặc bạn muốn tạo nhiều instance độc lập (ví dụ trong lazy-loaded modules).
Hỏi: Làm thế nào để kiểm thử dịch vụ có phụ thuộc?
Đáp: Dùng TestBed.configureTestingModule để cung cấp phiên bản mock cho các dependency.