Service và Dependency Injection trong Angular

Service là một danh mục rộng bao gồm bất kỳ giá trị, chức năng hoặc tính năng nào mà ứng dụng cần. Service thường là một lớp có mục đích hẹp, được xác định rõ ràng. Nó nên làm một cái gì đó cụ thể và làm nó tốt.

Angular phân tách các component với các service để tăng tính module và khả năng tái sử dụng. Bằng cách tách chức năng liên quan đến view của một component khỏi các loại xử lý khác, bạn có thể làm cho các lớp component của mình gọn gàng và hiệu quả.

Lý tưởng nhất, công việc của một component là kích hoạt trải nghiệm người dùng và không có gì hơn. Một component phải trình bày các thuộc tính và phương thức để liên kết dữ liệu, để làm trung gian giữa view (được hiển thị bởi template) và logic ứng dụng (thường bao gồm một số khái niệm về model).

Một component có thể ủy quyền các tác vụ nhất định cho các service, chẳng hạn như tìm nạp dữ liệu từ máy chủ, xác thực đầu vào của người dùng hoặc ghi trực tiếp vào console.

Bằng cách định nghĩa các tác vụ xử lý như vậy trong một lớp service có thể inject, bạn làm cho các tác vụ đó khả dụng cho bất kỳ component nào. Bạn cũng có thể làm cho ứng dụng của mình dễ thích ứng hơn bằng cách inject các nhà cung cấp khác nhau của cùng loại service, nếu thích hợp trong các trường hợp khác nhau.

Angular không thực thi các nguyên tắc này. Angular giúp bạn tuân theo các nguyên tắc này bằng cách dễ dàng đưa logic ứng dụng của bạn vào các service và cung cấp các service đó cho các component thông qua việc dependency injection.

Ví dụ về Service

Đây là một ví dụ về lớp service Logger ghi log vào console của trình duyệt.

export class Logger {
  log(msg: any)   { console.log(msg); }
  error(msg: any) { console.error(msg); }
  warn(msg: any)  { console.warn(msg); }
}

Các service có thể phụ thuộc vào các service khác. Ví dụ, service HeroService dưới đây phụ thuộc vào service Logger để ghi log và service BackendService để có được danh sách các anh hùng. Đến lượt BackendService, service này có thể phụ thuộc vào service HttpClient để tìm nạp danh sách các anh hùng bất đồng bộ (async) từ một máy chủ.

export class HeroService {
  private heroes: Hero[] = [];

  constructor(
    private backend: BackendService,
    private logger: Logger) { }

  getHeroes() {
    this.backend.getAll(Hero).then( (heroes: Hero[]) => {
      this.logger.log(`Fetched ${heroes.length} heroes.`);
      this.heroes.push(...heroes); // fill cache
    });
    return this.heroes;
  }
}

Dependency Injection

Dependency Injection (DI) được tích hợp sẵn trong framework Angular và được sử dụng ở mọi nơi để cung cấp cho các component mới với instance của các service hoặc những thứ khác mà chúng cần. Các component inject các service; nghĩa là bạn có thể inject một service vào trong một component, cấp cho component đó quyền truy cập vào lớp service đó.

Để xác định một lớp như một service trong Angular, hãy sử dụng decorator @Injectable() để cung cấp siêu dữ liệu cho phép Angular đưa nó vào một component như một phần phụ thuộc. Tương tự, sử dụng decorator @Injectable() để chỉ ra rằng một component hoặc lớp khác (chẳng hạn như một service khác, một pipe hoặc một NgModule) có một phần phụ thuộc.

  • Injector là cơ chế chính. Angular tạo một injector toàn ứng dụng cho bạn trong quá trình khởi động và bổ sung các injector khi cần thiết. Bạn không cần phải tạo injector.
  • Injector tạo ra các phụ thuộc và duy trì một vùng chứa các instance phụ thuộc mà nó sẽ sử dụng lại nếu có thể.
  • Provider là một đối tượng cho injector biết cách lấy hoặc khởi tạo một phụ thuộc.

Đối với bất kỳ phần phụ thuộc nào bạn cần trong ứng dụng của mình, bạn phải đăng ký một provider với injector của ứng dụng để injector có thể sử dụng provider đó để tạo các instance mới. Đối với một service, provider thường là chính lớp service đó.

Một phần phụ thuộc không nhất thiết phải là một service — ví dụ: nó có thể là một hàm hoặc một giá trị.

Khi Angular tạo một instance mới của một lớp component, nó sẽ xác định các service hoặc các phụ thuộc khác mà component cần bằng cách xem xét các tham số của phương thức khởi tạo. Ví dụ, phương thức constructor của component HeroListComponent có service HeroService.

constructor(private service: HeroService) { }

Khi Angular phát hiện ra rằng một component phụ thuộc vào một service, trước tiên nó sẽ kiểm tra xem injector có bất kỳ instance nào hiện có của service đó hay không.

Nếu một instance của service được yêu cầu chưa tồn tại, injector tạo một instance bằng cách sử dụng provider đã đăng ký và thêm nó vào injector trước khi trả lại instance của service cho Angular.

Khi tất cả các service được yêu cầu đã được khởi tạo và trả về, Angular có thể gọi hàm khởi tạo của component với instance của các service đó làm đối số.

Quá trình inject service HeroService trông giống như thế này.

Dependency Injection trong Angular

Cung cấp các Service

Bạn phải đăng ký ít nhất một provider của bất kỳ service nào bạn sẽ sử dụng. Provider có thể là một phần của siêu dữ liệu riêng của service, làm cho service đó khả dụng ở mọi nơi hoặc bạn có thể đăng ký provider với các module hoặc component cụ thể. Bạn đăng ký cung cấp service trong siêu dữ liệu của service (trong decorator @Injectable()) hoặc trong siêu dữ liệu của @NgModule() hoặc @Component().

1. Theo mặc định, lệnh Angular CLI ng generate service đăng ký một provider với bộ nạp gốc cho service của bạn bằng cách đưa siêu dữ liệu của provider vào decorator @Injectable(). Hướng dẫn sử dụng phương thức này để đăng ký provider định nghĩa lớp HeroService.

@Injectable({
 providedIn: 'root',
})

Khi bạn cung cấp service ở cấp cơ sở, Angular sẽ tạo một instance duy nhất, dùng chung của HeroService và đưa nó vào bất kỳ lớp nào yêu cầu nó. Đăng ký provider trong siêu dữ liệu của @Injectable() cũng cho phép Angular tối ưu hóa ứng dụng bằng cách xóa service khỏi ứng dụng đã biên dịch nếu nó không được sử dụng, quá trình này được gọi là tree-shaking.

2. Khi bạn đăng ký một provider với một NgModule cụ thể, thì tất cả các component trong NgModule đó sẽ sử dụng cùng một instance của service. Để đăng ký ở cấp độ này, hãy sử dụng thuộc tính providers của decorator @NgModule():

@NgModule({
   providers: [
     BackendService,
     Logger
  ],
  ...
})

3. Khi bạn đăng ký một provider ở cấp component, bạn sẽ nhận được một instance mới của service với mỗi instance mới của component đó. Ở cấp độ component, hãy đăng ký một provider của service trong thuộc tính providers của siêu dữ liệu @Component():

@Component({
  selector:    'app-hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})
Để biết thêm thông tin chi tiết, hãy xem bài viết Dependency Injection.
AngularTypeScript
Bài Viết Liên Quan:
Trình khởi động bootstrap trong Angular
Trung Nguyen 26/07/2021
Trình khởi động bootstrap trong Angular

Trong hướng dẫn này, chúng ta sẽ tìm hiểu về trình khởi động bootstrap và cách Angular hoạt động bên trong và khởi động ứng dụng của chúng ta.

Cách tạo một dự án mới trong Angular
Trung Nguyen 25/07/2021
Cách tạo một dự án mới trong Angular

Trong hướng dẫn này, chúng tôi sẽ chỉ cho bạn cách sử dụng Angular CLI để tạo một dự án mới trong Angular.

Không gian làm việc và Cấu trúc file dự án
Trung Nguyen 22/07/2021
Không gian làm việc và Cấu trúc file dự án

Trong hướng dẫn này, bạn sẽ tìm hiểu về không dan làm việc và cấu trúc file dự án ứng dụng, dự án thư viện của Angular.

Component và Template trong Angular
Trung Nguyen 20/07/2021
Component và Template trong Angular

Trong hướng dẫn này, bạn sẽ tìm hiểu mọi thứ về component trong Angular như: metadata, template và view, data binding, directive, ...