Tại sao không phải lúc nào cũng cần sử dụng "providers" để inject service trong Angular?

Khi nào cần sử dụng providers? Tìm hiểu cách sử dụng providedIn và khi nào bạn nên hoặc không nên khai báo providers trong Angular.

Trong quá trình phát triển ứng dụng Angular, một trong những khía cạnh quan trọng mà các lập trình viên thường phải đối mặt là Dependency Injection (DI) — cơ chế giúp quản lý các dịch vụ (services) trong suốt vòng đời của ứng dụng. Một câu hỏi phổ biến mà nhiều người đặt ra là: "Khi nào cần sử dụng providers trong Angular?" Đôi khi, bạn có thể inject một service mà không cần khai báo nó trong providers. Vậy tại sao điều đó xảy ra? Bài viết này sẽ giải đáp cho bạn.

Cơ chế providers trong Angular là gì?

Trong Angular, providers là một mảng cấu hình để khai báo các dịch vụ mà bạn muốn sử dụng trong một module hoặc component. Điều này giúp Angular biết cách tạo các service và cung cấp chúng khi cần thiết (thường thông qua Dependency Injection).

Tuy nhiên, không phải lúc nào bạn cũng cần phải khai báo các service trong providers để có thể sử dụng chúng trong component. Điều này phụ thuộc vào cách bạn định nghĩa service của mình.

Sử dụng providedIn với @Injectable

Angular cung cấp một cách tiếp cận đơn giản và tối ưu để quản lý các service thông qua thuộc tính providedIn trong decorator @Injectable. Khi bạn định nghĩa service với providedIn: 'root', Angular sẽ tự động tạo một instance của service đó và cung cấp nó cho toàn bộ ứng dụng.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class MyService {
  getMessage() {
    return 'Hello from MyService!';
  }
}

Với providedIn: 'root', MyService sẽ được cung cấp ở cấp toàn cục. Điều này có nghĩa là bạn không cần phải khai báo nó trong providers của từng module hay component mà vẫn có thể inject vào mọi nơi trong ứng dụng.

Khi nào bạn không cần providers?

Nếu service đã được định nghĩa với providedIn: 'root' hoặc được khai báo trong mảng providers của module (ví dụ: AppModule), bạn có thể sử dụng service mà không cần phải thêm nó vào providers trong từng component.

Ví dụ khi sử dụng MyService trong một component mà không cần providers:

import { Component } from '@angular/core';
import { MyService } from './my-service';

@Component({
  selector: 'app-my-component',
  template: '<p>{{ message }}</p>',
})
export class MyComponent {
  message: string;

  constructor(private myService: MyService) {
    this.message = this.myService.getMessage();
  }
}

Ở đây, MyService được cung cấp sẵn trong toàn bộ ứng dụng nhờ providedIn: 'root', nên bạn không cần phải khai báo nó trong providers của MyComponent.

Khi nào cần sử dụng providers trong Component?

Mặc dù không phải lúc nào bạn cũng cần sử dụng providers, nhưng vẫn có những trường hợp bạn cần đến nó. Điều này xảy ra khi:

  • Bạn muốn tạo một phiên bản riêng biệt của service cho component: Mỗi khi bạn khai báo một service trong providers của component, Angular sẽ tạo ra một instance mới của service đó chỉ dành riêng cho component và các component con của nó.
  • Ghi đè một dịch vụ đã tồn tại: Nếu bạn muốn sử dụng một phiên bản khác của một service cho một component cụ thể, bạn có thể ghi đè service đó thông qua providers.

Ví dụ:

@Component({
  selector: 'app-custom-component',
  template: '<p>{{ message }}</p>',
  providers: [{ provide: MyService, useClass: CustomService }], // Ghi đè MyService bằng CustomService
})
export class CustomComponent {
  message: string;

  constructor(private myService: MyService) {
    this.message = this.myService.getMessage();
  }
}

Trong ví dụ trên, MyService được thay thế bằng CustomService chỉ cho CustomComponent. Các component khác trong ứng dụng vẫn sẽ sử dụng MyService gốc.

Ưu điểm của providedIn: 'root'

Sử dụng providedIn: 'root' có một số lợi ích chính:

  • Đơn giản hóa việc quản lý dịch vụ: Bạn không cần phải nhớ khai báo service trong providers của từng component hay module.
  • Hiệu quả về bộ nhớ: Angular sẽ chỉ tạo một instance của service trong toàn bộ ứng dụng, giúp tiết kiệm bộ nhớ và tránh việc tạo nhiều bản sao không cần thiết của cùng một service.
  • Dễ dàng bảo trì: Bạn có thể quản lý và cập nhật service một cách dễ dàng hơn khi biết rằng nó đã được cung cấp sẵn trong toàn bộ ứng dụng.

Khi nào nên sử dụng providers Ở cấp Module?

Ngoài việc cung cấp service ở cấp component hoặc toàn bộ ứng dụng, bạn cũng có thể cung cấp chúng ở cấp module. Điều này hữu ích khi bạn muốn giới hạn việc sử dụng service trong một nhóm component cụ thể.

Ví dụ trong AppModule:

@NgModule({
  providers: [MyService],
})
export class AppModule {}

Khi khai báo như vậy, tất cả các component trong AppModule sẽ dùng chung một instance của MyService.

Kết luận

Bạn không phải lúc nào cũng cần sử dụng providers để inject một service trong Angular. Trong hầu hết các trường hợp, khi sử dụng providedIn: 'root', service sẽ tự động được cung cấp ở cấp toàn bộ ứng dụng và có thể được inject vào bất kỳ đâu mà không cần khai báo thêm trong providers. Tuy nhiên, khi bạn cần tạo một phiên bản riêng của service cho một component hoặc module, hoặc khi cần ghi đè một dịch vụ đã tồn tại, providers vẫn đóng vai trò quan trọng.

Sử dụng đúng providers giúp ứng dụng của bạn linh hoạt hơn và dễ bảo trì hơn. Điều quan trọng là hiểu rõ khi nào cần và không cần sử dụng providers để tối ưu hóa việc quản lý dịch vụ trong ứng dụng Angular của bạn.