Tìm kiếm trang web

typescript - Mục đích của providedIn với decorator Injectable khi tạo Services trong Angular 6 là gì?


Khi tạo dịch vụ trong Angular CLI, nó đang thêm siêu dữ liệu bổ sung với thuộc tính 'provided in' với mặc định là 'root' cho decorator Injectable.

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

Cái providedIn thực sự làm gì? Tôi đang giả sử điều này giúp dịch vụ có sẵn như một dịch vụ singleton kiểu 'toàn cầu' cho toàn bộ ứng dụng, tuy nhiên, liệu không sạch sẽ hơn nếu khai báo các dịch vụ như vậy trong mảng provider của AppModule?

Câu trả lời #1

providedIn: 'root' là cách dễ nhất và hiệu quả nhất để cung cấp dịch vụ kể từ Angular 6:

  1. Dịch vụ sẽ có sẵn trên toàn ứng dụng như một singleton mà không cần thêm nó vào mảng providers của module (như Angular <= 5).
  2. Nếu dịch vụ chỉ được sử dụng trong một mô-đun tải lười biếng, nó sẽ được tải lười biếng cùng với mô-đun đó
  3. Nếu nó không bao giờ được sử dụng, nó sẽ không được đưa vào bản dựng (tree shaked).

Để biết thêm thông tin, hãy xem xét đọc tài liệu và phần Câu hỏi thường gặp về NgModule

Nhân tiện :

  1. Nếu bạn không muốn một singleton áp dụng cho toàn bộ ứng dụng, hãy sử dụng mảng của nhà cung cấp trong một thành phần thay vào đó.
  2. Nếu bạn muốn giới hạn phạm vi để không có nhà phát triển nào khác sử dụng dịch vụ của bạn ngoài một module cụ thể, hãy sử dụng mảng providers của NgModule thay vào đó.

Câu trả lời #2

Từ Tài liệu

Injectable decorator là gì?

Đánh dấu một lớp là có sẵn cho Injector để tạo.

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

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

Dịch vụ tự nó là một lớp mà CLI tạo ra và được trang trí với @Injectable().

providedIn thực sự làm gì?

Xác định bộ tiêm nào sẽ cung cấp injectable, bằng cách liên kết nó với một @NgModule hoặc loại Injector khác, hoặc bằng cách chỉ định rằng injectable này nên được cung cấp trong bộ tiêm 'root', đây sẽ là bộ tiêm cấp ứng dụng trong hầu hết các ứng dụng.

providedIn: Type<any> | 'root' | null

providedIn: 'root'

Khi bạn cung cấp dịch vụ ở cấp độ gốc, Angular tạo một thể hiện duy nhất, được chia sẻ của dịch vụ và tiêm nó vào bất kỳ lớp nào yêu cầu. Đăng ký nhà cung cấp trong siêu dữ liệu @Injectable() cũng cho phép Angular tối ưu hóa ứng dụng bằng cách loại bỏ dịch vụ khỏi ứng dụng đã biên dịch nếu nó không được sử dụng.

providedIn: Module

Cũng có thể chỉ định rằng một dịch vụ nên được cung cấp trong một @NgModule cụ thể. Ví dụ, nếu bạn không muốn một dịch vụ có sẵn cho các ứng dụng trừ khi họ nhập một module mà bạn đã tạo, bạn có thể chỉ định rằng dịch vụ đó nên được cung cấp trong module đó

import { Injectable } from '@angular/core';
import { UserModule } from './user.module';

@Injectable({
  providedIn: UserModule,
})
export class UserService {
}

Phương pháp này được ưa chuộng vì nó cho phép Tree-shaking (Tree shaking là một bước trong quá trình xây dựng loại bỏ mã không sử dụng khỏi cơ sở mã) của dịch vụ nếu không có gì tiêm nó vào.

Nếu không thể chỉ định trong dịch vụ module nào nên cung cấp nó, bạn cũng có thể khai báo một nhà cung cấp cho dịch vụ trong module:

import { NgModule } from '@angular/core';
import { UserService } from './user.service';

@NgModule({
  providers: [UserService],
})
export class UserModule {
}

Câu trả lời #3

Đây là một câu trả lời mới, cập nhật với Angular 9+ (sau Ivy) và nên đúng vào năm 2022.


TL;DR: Tất cả đều liên quan đến việc kiểm soát có bao nhiêu bản sao của dịch vụ của bạn sẽ được tạo ra, và nơi nào chúng sẽ có sẵn sau khi được tạo.


Thuật ngữ:

  • Có thể tiêm - bất kỳ lớp nào được trang trí với @Injectable, ví dụ như một dịch vụ.

  • bộ tiêm - một lớp Angular có khả năng cung cấp các Injectable cho các lớp bên dưới nó. (Điều này bao gồm tất cả các component và module.)

  • phạm vi/cấp độ của bộ tiêm - phạm vi của tất cả các thể hiện lớp sống "dưới" một bộ tiêm cụ thể.

  • cấu trúc kim tiêm - một cây các phạm vi kim tiêm được ưu tiên, sắp xếp theo thứ tự nền tảng -> gốc -> mô-đun -> thành phần.

  • Injectable được cung cấp - một thể hiện của Injectable sẽ được đưa cho các lớp dưới cấp độ bộ tiêm cụ thể này, mỗi khi họ yêu cầu nó.

  • Injectable là được tiêm - một constructor của lớp đã yêu cầu được nhận một số instance của dịch vụ, vì vậy Angular sẽ cố gắng cung cấp instance gần nhất có thể được tìm thấy trong thứ bậc injector.

  • tree-shaking - một tối ưu hóa xảy ra tự động nhờ trình biên dịch Angular. Khi nó phát hiện rằng một số mã không được sử dụng, mã đó sẽ bị loại bỏ khỏi bản biên dịch cuối cùng của ứng dụng (hoặc biên dịch của một module tải chậm cụ thể).

Các thuật ngữ khác mà bạn nên biết: lớp, thể hiện, mô-đun, thành phần, các mô-đun được tải lười biếng/nhanh chóng.


Hỏi: Chính xác thì providedIn làm gì?

Đây là một cài đặt xác định bộ tiêm nào nên cung cấp Injectable của bạn.

Hãy giả sử chúng ta tạo một Injectable gọi là MyService, và đi qua tất cả các tùy chọn xem chúng hoạt động như thế nào.

providedIn: Type<any> | 'root' | 'platform' | 'any' | null
providedIn: 'platform'

Angular sẽ tạo và cung cấp một thể hiện chia sẻ duy nhất của MyService cho tất cả các ứng dụng Angular trên trang. (Điều này chỉ liên quan trong các trường hợp sử dụng nâng cao, nếu bạn sử dụng kiến trúc micro-frontends.)

providedIn: 'root'

Angular sẽ tạo một phiên bản duy nhất dùng chung của MyService và cung cấp nó cho tất cả các lớp trong ứng dụng.

providedIn: 'any'/KHÔNG CÒN SỬ DỤNG kể từ Angular v15

Angular sẽ tạo một phiên bản dùng chung duy nhất của MyService và cung cấp nó cho tất cả các lớp trong các mô-đun được tải ngay lập tức.

Tuy nhiên, mỗi mô-đun tải lười sẽ cung cấp một thể hiện riêng, mới, tách biệt của MyService (sau đó chỉ có sẵn trong các lớp bên trong mô-đun đó).

providedIn: MyModule/BỎ SỬ DỤNG kể từ Angular v15

Angular sẽ chỉ tạo một thể hiện của MyService nếu MyModule được tải.

Nếu MyModule được tải hoá nôn nóng, thì thể hiện đó sẽ có sẵn cho tất cả các module được tải hoá nôn nóng khác từ bây giờ. (Lưu ý rằng điều này về cơ bản giống hệt với providedIn: 'root'.)

Tuy nhiên, nếu MyModule được tải lười, thì thể hiện này sẽ chỉ được cung cấp cho các lớp bên trong MyModule, bất cứ khi nào nó được tải.

providedIn: MyComponent/BỎ SỬ DỤNG kể từ Angular v15

Angular sẽ tạo một thể hiện mới, tươi mới của MyService mỗi khi MyComponent được khởi tạo.

Thể hiện MyService này sẽ chỉ được cung cấp cho các hậu duệ của MyComponent instance cụ thể đó, và sẽ bị hủy ngay khi thể hiện của component bị hủy. (Lưu ý rằng điều này có nghĩa là một MyService mới sẽ được tạo ra mỗi lần component này được render.)

providedIn: null

MyService chỉ có thể được khởi tạo bằng cách thêm vào mảng providers trong một module hoặc component cụ thể.

Bất cứ khi nào mô-đun/thành phần đó được khởi tạo, nó sẽ tạo một thể hiện mới của MyService, và chỉ cung cấp nó trong phạm vi cụ thể của nó. (Xem mô tả đầy đủ của mảng providers bên dưới.)


Hỏi: Mảng providers làm gì?

Bất kỳ bộ tiêm nào cũng có thể được thiết lập với một mảng providers:

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

@Component({
  providers: [MyService],
})

Tất cả các Injectable có thể được thêm vào một mảng providers, bất kể cài đặt providedIn của chúng.

Thêm MyService vào mảng providers sẽ khiến bộ tiêm tạo và cung cấp một thể hiện hoàn toàn riêng biệt của nó cho các lớp trong phạm vi của nó. (Phạm vi chính xác giống như được mô tả trong các ví dụ providedIn: MyModule và providedIn: MyComponent ở trên.)

Phương pháp cung cấp này không hỗ trợ tree-shaking. Dịch vụ sẽ luôn được bao gồm trong quá trình biên dịch, ngay cả khi không ai sử dụng nó. (Xem các ghi chú về tree-shaking bên dưới.)

Hỏi: Tại sao tôi lại sử dụng mảng providers và providedIn cùng lúc?

Một ví dụ về trường hợp sử dụng có thể là nếu MyService được providedIn: 'root' và đã có một thể hiện được chia sẻ, nhưng bạn muốn module/component của mình có một thể hiện riêng, tách biệt.


Ghi chú bổ sung:

Hỏi: Cài đặt providedIn/providers ảnh hưởng như thế nào đến tree-shaking?

Một Injectable được cấu hình với providedIn sẽ bị loại bỏ trong quá trình tree-shaking nếu nó không được tiêm bởi bất kỳ lớp nào (được tải ngay lập tức hoặc tải theo nhu cầu) trong phạm vi injector được chỉ định của nó.

Tuy nhiên, một Injectable được gán vào một mảng providers trong một số module/component sẽ không bao giờ bị tree-shaken, ngay cả khi nó không được tiêm ở bất kỳ đâu.

Để việc tree-shaking hiệu quả nhất, bạn nên luôn hướng tới việc sử dụng providedIn thay vì mảng providers.

Q: Tại sao tôi lại sử dụng providedIn: 'root' nếu tôi nghĩ việc sử dụng mảng providers trong AppModule trông sạch sẽ hơn?

Như đã giải thích ở trên, điểm khác biệt chính là giữa hai phương pháp, providedIn hỗ trợ tree-shaking, còn mảng providers thì không.

Ngoài ra, đó là một quyết định kiến trúc: nếu chúng ta đặt providedIn trực tiếp trong tệp Injectable, Injectable sở hữu quyết định về cách nó nên được cung cấp. Việc phân biệt ai sở hữu hợp đồng có những tác động quan trọng đối với các ứng dụng và nhóm lớn phải phối hợp giữa hàng trăm mô-đun.

Hỏi: Có sự khác biệt nào giữa việc đặt mảng providers: [MyService] trong AppComponent hay AppModule không?

Đúng. MyService sẽ chỉ được cung cấp trong các mô-đun tải lười nếu bạn thực hiện nó trong AppModule, không phải AppComponent.

(Đó là bởi vì các mô-đun được tải lười dựa vào Router, được nhập trong AppModule, một phạm vi bộ tiêm cao hơn AppComponent.)

Câu trả lời #4

nếu bạn sử dụng providedIn, injectable sẽ được đăng ký như một provider của Module mà không cần thêm nó vào providers của module.

Từ Docs

Dịch vụ tự nó là một lớp mà CLI tạo ra và được trang trí bằng @Injectable. Theo mặc định, trình trang trí này được cấu hình với thuộc tính providedIn, điều này tạo một nhà cung cấp cho dịch vụ. Trong trường hợp này, providedIn: 'root' chỉ định rằng dịch vụ nên được cung cấp trong bộ tiêm gốc.

Câu trả lời #5

xem Giải thích xuất sắc của @Nipuna,

Tôi muốn mở rộng nó bằng cách thêm các ví dụ.

nếu bạn chỉ sử dụng decorator Injectable mà không có thuộc tính providedIn, như

@Injectable()

sau đó bạn sẽ phải viết tên dịch vụ vào mảng providers của Module tương ứng.

như thế này;

data.service.ts ↴

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

@Injectable()
export class DataService {
    constructor() {}

    // Code . . .
}

app.module.ts ↴

import { AppComponent } from './app.component';
import { DataService } from './core/data.service';

@NgModule({
    declarations: [AppComponent],
    providers: [DataService],    // ⟵ LOOK HERE WE PROVIDED IT
    imports: [...],
    bootstrap: [AppComponent],
})
export class AppModule {}

Nhưng, nếu bạn sử dụng providedIn: 'root', như thế này:

data.service.ts ↴

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

@Injectable({
    providedIn: 'root',
})
export class DataService {
    constructor() {}

    // Code . . .
}

Vậy module của chúng ta sẽ trông như thế này:

app.module.ts ↴

import { AppComponent } from './app.component';
import { DataService } from './core/data.service';

@NgModule({
    declarations: [AppComponent],
    providers: [],
    imports: [...],
    bootstrap: [AppComponent],
})
export class AppModule {}

thấy không, lần này tôi đã không thêm DataService vào mảng providers, vì nó không cần thiết.

Thực hành tốt

Điều này có thể hữu ích, từ Hướng dẫn Angular

Hãy cung cấp một dịch vụ với bộ tiêm gốc của ứng dụng trong trình trang trí @Injectable của dịch vụ.

Tại sao? Bộ tiêm Angular có cấu trúc phân cấp.

Tại sao? Khi bạn cung cấp dịch vụ cho một root injector, phiên bản dịch vụ đó được chia sẻ và có sẵn trong mọi lớp cần dịch vụ. Điều này lý tưởng khi một dịch vụ đang chia sẻ các phương thức hoặc trạng thái.

Tại sao? Khi bạn đăng ký một dịch vụ trong decorator @Injectable của dịch vụ, các công cụ tối ưu hóa chẳng hạn như những công cụ được sử dụng bởi bản build production của Angular CLI có thể thực hiện tree shaking và loại bỏ các dịch vụ không được ứng dụng của bạn sử dụng.

Tại sao? Điều này không lý tưởng khi hai thành phần khác nhau cần các phiên bản khác nhau của một dịch vụ. Trong trường hợp này, sẽ tốt hơn nếu cung cấp dịch vụ ở cấp thành phần cần phiên bản mới và riêng biệt.

Câu trả lời số 6

providedIn cho Angular biết rằng injector gốc chịu trách nhiệm tạo một thể hiện của Service của bạn. Các Service được cung cấp theo cách này sẽ tự động có sẵn cho toàn bộ ứng dụng và không cần phải được liệt kê trong bất kỳ module nào.

Các lớp dịch vụ có thể tự hoạt động như nhà cung cấp của chính chúng, đó là lý do tại sao việc định nghĩa chúng trong decorator @Injectable là tất cả những gì bạn cần để đăng ký.

Bài viết liên quan