Angular 獨立組件

2022-07-21 09:53 更新

獨立組件入門

在 v14 及更高版本中,獨立組件提供了一種簡化的方式來構(gòu)建 Angular 應(yīng)用程序。獨立組件、指令和管道旨在通過減少對 ?NgModule ?的需求來簡化創(chuàng)作體驗?,F(xiàn)有應(yīng)用程序可以選擇性地以增量方式采用新的獨立風格,而無需任何重大更改。

獨立組件特性可用于開發(fā)人員預(yù)覽。它已準備好供你嘗試;但它可能會在穩(wěn)定之前發(fā)生變化。

創(chuàng)建獨立組件

standalone 標志和組件 imports

組件、指令和管道現(xiàn)在可以標記為 ?standalone: true?。標記為獨立的 Angular 類不需要在 ?NgModule ?中聲明(如果你嘗試,Angular 編譯器會報告錯誤)。

獨立組件直接指定它們的依賴項,而不是通過 ?NgModule ?獲取它們。例如,如果 ?PhotoGalleryComponent ?是獨立組件,它可以直接導(dǎo)入另一個獨立組件 ?ImageGridComponent ?:

@Component({
  standalone: true,
  selector: 'photo-gallery',
  imports: [ImageGridComponent],
  template: `
    ... <image-grid [images]="imageList"></image-grid>
  `,
})
export class PhotoGalleryComponent {
  // component logic
}

?imports ?也可用于引用獨立指令和管道。通過這種方式,可以編寫?yīng)毩⒔M件,而無需創(chuàng)建 ?NgModule ?來管理模板依賴項。

在獨立組件中使用現(xiàn)有的 NgModules

編寫?yīng)毩⒔M件時,你可能希望在組件的模板中使用其他組件、指令或管道。其中某些依賴項可能不會標記為獨立,而是由現(xiàn)有的 ?NgModule ?聲明和導(dǎo)出。在這種情況下,你可以將 ?NgModule ?直接導(dǎo)入到獨立組件中:

@Component({
  standalone: true,
  selector: 'photo-gallery',
  // an existing module is imported directly into a standalone component
  imports: [MatButtonModule],
  template: `
    ...
    <button mat-button>Next Page</button>
  `,
})
export class PhotoGalleryComponent {
  // logic
}

你可以在模板中將獨立組件與現(xiàn)有的基于 ?NgModule ?的庫或依賴項一起使用。獨立組件可以充分利用現(xiàn)有的 Angular 庫生態(tài)系統(tǒng)。

在基于 NgModule 的應(yīng)用程序中使用獨立組件

獨立組件也可以導(dǎo)入到現(xiàn)有的基于 NgModules 的上下文中。這允許現(xiàn)有應(yīng)用程序(今天使用 NgModules)逐步采用新的獨立風格的組件。

你可以像導(dǎo)入 ?NgModule ?一樣導(dǎo)入獨立組件(或指令或管道)- 使用 ?NgModule.imports? :

@NgModule({
  declarations: [AlbumComponent],
  exports: [AlbumComponent], 
  imports: [PhotoGalleryComponent],
})
export class AlbumModule {}

使用獨立組件引導(dǎo)應(yīng)用程序

通過使用獨立組件作為應(yīng)用程序的根組件,可以在沒有任何 ?NgModule ?的情況下引導(dǎo) Angular 應(yīng)用程序。這是使用 ?bootstrapApplication ?API 來完成的:

// in the main.ts file
import {bootstrapApplication} from '@angular/platform-browser';
import {PhotoAppComponent} from './app/photo.app.component';

bootstrapApplication(PhotoAppComponent);

配置依賴注入

引導(dǎo)應(yīng)用程序時,你通常希望配置 Angular 的依賴注入并提供配置值或服務(wù)以在整個應(yīng)用程序中使用。你可以將這些作為提供者傳遞給 ?bootstrapApplication ?:

bootstrapApplication(PhotoAppComponent, {
  providers: [
    {provide: BACKEND_URL, useValue: 'https://photoapp.looknongmodules.com/api'},
    // ...
  ]
});

獨立的引導(dǎo)操作基于顯式配置 ?Provider ?列表以進行依賴注入。但是,現(xiàn)有的庫可能依賴 ?NgModule ?來配置 DI。例如,Angular 的路由器使用 ?RouterModule.forRoot()? 幫助器在應(yīng)用程序中設(shè)置路由。你可以通過 ?importProvidersFrom ?實用程序在 ?bootstrapApplication ?中使用這些現(xiàn)有的 ?NgModule ?:

bootstrapApplication(PhotoAppComponent, {
  providers: [
    {provide: BACKEND_URL, useValue: 'https://photoapp.looknongmodules.com/api'},
    importProvidersFrom(
      RouterModule.forRoot([/* app routes */]),
    ),
    // ...
  ]
});

路由和惰性加載

路由器 API 進行了更新和簡化,以利用獨立組件的優(yōu)勢:在許多常見的惰性加載場景中不再需要 ?NgModule?。

惰性加載獨立組件

任何路由都可以用 ?loadComponent ?惰性加載其路由到的獨立組件:

export const ROUTES: Route[] = [
  {path: 'admin', loadComponent: () => import('./admin/panel.component').then(mod => mod.AdminPanelComponent)},
  // ...
];

只要加載的組件是獨立的,就可以用。

一次惰性加載多個路由

?loadChildren ?操作現(xiàn)在支持加載一組新的子 ?Route?,而無需編寫惰性加載的 ?NgModule ?來導(dǎo)入 ?RouterModule.forChild? 來聲明路由。當以這種方式加載的每個路由都使用獨立組件時,這會起作用。

// In the main application:
export const ROUTES: Route[] = [
  {path: 'admin', loadChildren: () => import('./admin/routes').then(mod => mod.ADMIN_ROUTES)},
  // ...
];

// In admin/routes.ts:
export const ADMIN_ROUTES: Route[] = [
  {path: 'home', component: AdminHomeComponent},
  {path: 'users', component: AdminUsersComponent},
  // ...
];

為路由的子集提供服務(wù)

?NgModule ?的惰性加載 API ( ?loadChildren ?) 在加載路由的惰性加載的子項時會創(chuàng)建一個新的“模塊”注入器。此特性通??捎糜趦H向應(yīng)用程序中的一部分路由提供服務(wù)。例如,如果 ?/admin? 下的所有路由都使用 ?loadChildren ?邊界來限定范圍,則可以僅向這些路由提供僅限管理的服務(wù)。執(zhí)行此操作需要使用 ?loadChildren ?API,即使惰性加載有問題的路由不是必要的。

路由器現(xiàn)在支持在 ?Route ?上顯式指定其他 ?providers?,這允許相同的范圍限定,而無需惰性加載或 ?NgModule?。例如,?/admin? 路由結(jié)構(gòu)中的范圍服務(wù)將類似于:

export const ROUTES: Route[] = [
  {
    path: 'admin',
    providers: [
      AdminService,
      {provide: ADMIN_API_KEY, useValue: '12345'},
    ],
    children: [
      path: 'users', component: AdminUsersComponent,
      path: 'teams', component: AdminTeamsComponent,
    ],
  },
  // ... other application routes that don't
  //     have access to ADMIN_API_KEY or AdminService.
];

也可以將 ?providers ?與額外路由配置的 ?loadChildren ?結(jié)合使用,以實現(xiàn)與惰性加載帶有額外路由和路由級服務(wù)提供者的 ?NgModule ?相同的效果。此示例配置與上面相同的提供者/子路由,但在惰性加載邊界之后:

// Main application:
export const ROUTES: Route[] = {
  // Lazy-load the admin routes.
  {path: 'admin', loadChildren: () => import('./admin/routes').then(mod => mod.ADMIN_ROUTES)},
  // ... rest of the routes
}

// In admin/routes.ts:
export const ADMIN_ROUTES: Route[] = [{
  path: '',
  pathMatch: 'prefix',
  providers: [
    AdminService,
    {provide: ADMIN_API_KEY, useValue: 12345},
  ],
  children: [
    {path: 'users', component: AdminUsersCmp},
    {path: 'teams', component: AdminTeamsCmp},
  ],
}];

請注意這里使用了空路徑路由來定義供所有子路由共享的宿主 ?providers?。

高級主題

本節(jié)會更詳細地介紹僅與更高級的使用模式相關(guān)的。第一次了解獨立組件、指令和管道時,你可以安全地跳過本節(jié)。

針對庫作者的獨立組件

獨立的組件、指令和管道可以從導(dǎo)入它們的 ?NgModule ?中導(dǎo)出:

@NgModule({
  imports: [ImageCarouselComponent, ImageSlideComponent],
  exports: [ImageCarouselComponent, ImageSlideComponent],
})
export class CarouselModule {}

此模式對于發(fā)布一組合作指令的 Angular 庫很有用。在上面的示例中,?ImageCarouselComponent ?和 ?ImageSlideComponent ?需要出現(xiàn)在模板中,以構(gòu)建一個邏輯上的“輪播小部件”。

作為發(fā)布 ?NgModule ?的替代方案,庫作者可能希望導(dǎo)出一個合作指令數(shù)組:

export CAROUSEL_DIRECTIVES = [ImageCarouselComponent, ImageSlideComponent] as const;

這樣的數(shù)組可以由使用 ?NgModule ?的應(yīng)用程序?qū)氩⑻砑拥?nbsp;?@NgModule.imports?。請注意 TypeScript 的 ?as const? 構(gòu)造的存在:它為 Angular 編譯器提供了正確編譯所需的額外信息,并且是一種推薦的實踐(因為它使導(dǎo)出的數(shù)組從 TypeScript 的角度來看是不可變的)。

依賴注入和注入器層次結(jié)構(gòu)

Angular 應(yīng)用程序可以通過指定一組可用的提供者來配置依賴注入。在典型應(yīng)用中,有兩種不同的注入器類型:

  • 具有在 ?@NgModule.providers? 或 ?@Injectable({providedIn: "..."})? 中配置的服務(wù)提供者的模塊注入器。這些應(yīng)用程序范圍的提供者對模塊注入器中配置的所有組件以及其他服務(wù)可見。
  • 在 ?@Directive.providers? / ?@Component.providers? 或 ?@Component.viewProviders? 中配置的節(jié)點注入器。這些提供程序僅對給定組件及其所有子項可見。

環(huán)境注入器

使 ?NgModule ?變成可選的將需要一種新方法來用應(yīng)用程序范圍的提供者配置“模塊”注入器。在獨立應(yīng)用程序(使用 ?bootstrapApplication ?創(chuàng)建的)中,可以在引導(dǎo)過程中在 ?providers ?選項中配置“模塊”提供程序:

bootstrapApplication(PhotoAppComponent, {
  providers: [
    {provide: BACKEND_URL, useValue: 'https://photoapp.looknongmodules.com/api'},
    {provide: PhotosService, useClass: PhotosService},
    // ...
  ]
});

新的引導(dǎo) API 為我們提供了在不使用 ?NgModule ?的情況下配置“模塊注入器”的方法。從這個意義上說,名稱的“模塊”部分不再相關(guān),我們決定引入一個新術(shù)語:“環(huán)境注入器”。

可以用以下方法之一配置環(huán)境注入器:

  • ?@NgModule.providers?(在通過 ?NgModule ?引導(dǎo)的應(yīng)用程序中);
  • ?@Injectable({provideIn: "..."})?(在基于 NgModule 以及“獨立”應(yīng)用程序中);
  • ?bootstrapApplication ?調(diào)用中的 ?providers ?選項(在完全“獨立”的應(yīng)用程序中);
  • ?Route ?配置中的 ?providers ?字段。

Angular v14 引入了一種新的 TypeScript 類型 ?EnvironmentInjector ?來表示這種新命名。附帶的 ?createEnvironmentInjector ?API 使得以編程方式創(chuàng)建環(huán)境注入器成為可能:

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

const parentInjector = … // existing environment injector
const childInjector = createEnvironmentInjector([{provide: PhotosService, useClass: CustomPhotosService}], parentInjector);

環(huán)境注入器還有一個額外的能力:它們可以在創(chuàng)建環(huán)境注入器時執(zhí)行初始化邏輯(類似于創(chuàng)建模塊注入器時執(zhí)行的 ?NgModule ?構(gòu)造函數(shù)):

import {createEnvironmentInjector, ENVIRONMENT_INITIALIZER} from '@angular/core';

createEnvironmentInjector([
{provide: PhotosService, useClass: CustomPhotosService},
{provide: ENVIRONMENT_INITIALIZER, useValue: () => {
        console.log("This function runs when this EnvironmentInjector gets created");
}}
]);

獨立注入器

實際上,依賴注入器層次結(jié)構(gòu)在使用獨立組件的應(yīng)用程序中稍微復(fù)雜一些。讓我們考慮以下示例:

// an existing "datepicker" component with an NgModule
@Component({
  selector: 'datepicker',
  template: '...',
})
class DatePickerComponent {
  constructor(private calendar: CalendarService) {}
}

@NgModule({
  declarations: [DatePickerComponent],
  exports: [DatePickerComponent]
  providers: [CalendarService],
})
class DatePickerModule {
}

@Component({
  selector: 'date-modal',
  template: '<datepicker></datepicker>',
  standalone: true,
  imports: [DatePickerModule]
})
class DateModalComponent {
}

在上面的示例中,組件 ?DateModalComponent ?是獨立的 - 它可以直接使用,并且沒有需要導(dǎo)入才能使用它的 NgModule。但是,?DateModalComponent ?有一個依賴項 ?DatePickerComponent?,它是通過其 NgModule(?DatePickerModule?)導(dǎo)入的。此 NgModule 可以聲明 ?DatePickerComponent ?正常運行所需的提供者(在本例中為:?CalendarService?)。

當 Angular 創(chuàng)建獨立組件時,它需要知道當前注入器具有獨立組件依賴項的所有必要服務(wù),包括基于 NgModules 的服務(wù)。為了保證這一點,在某些情況下,Angular 會創(chuàng)建一個新的“獨立注入器”作為當前環(huán)境注入器的子項。今天,這種情況發(fā)生在所有引導(dǎo)的獨立組件上:它將是根環(huán)境注入器的子項。相同的規(guī)則適用于動態(tài)創(chuàng)建的(例如,由路由器或 ?ViewContainerRef ?API)獨立組件。

創(chuàng)建了一個單獨的獨立注入器,以確保獨立組件導(dǎo)入的提供程序與應(yīng)用程序的其余部分“隔離”。這讓我們將獨立組件視為真正獨立的部分,不能將它們的實現(xiàn)細節(jié)“泄漏”給應(yīng)用程序的其余部分。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號