Angular 15 独立组件详解

前言

在 Angular 15 中,独立 APIs 正式稳定,开发人员可以使用独立组件开发各种组件、指令、管道和构建应用程序。独立 APIs 减少了在开发时对 ngModule 的依赖,提升了开发体验。

目前,组件库 ngx-tethys 紧跟 Angular 脚步,现在已支持独立组件。

什么是独立组件

独立组件提供了一种简化的方式来构建 Angular 应用程序。独立组件、指令和管道旨在通过减少对 NgModule 的需求来简化创作体验。现有应用程序可以选择性地以增量方式采用新的独立风格,而无需任何重大更改。

如何创建独立组件

NgModule 创建组件

回顾下在独立 APIs 之前,创建组件、指令、管道需要完成哪些:

创建组件、指令、管道

创建 ngModule ,在此 ngModule 中需要:

通过 declarations 声明新创建的组件、指令和管道

通过 imports 导入在该模块中使用到的其他组件、指令、管道所属的模块

通过 exports 导出可供其他模块使用的组件、指令和管道

通过 providers 设置一些供组件使用的服务

比如创建一个按钮组件:

创建组件

复制代码
@Component({
    selector: 'button',
    templateUrl: './button.component.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ButtonComponent {}

定义 ButtonModule

复制代码
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';

import { ButtonComponent } from './button.component';

@NgModule({
    declarations: [ButtonComponent,],
    imports: [CommonModule],
    exports: [ButtonComponent,],
    providers: []
})
export class ButtonModule {}

每次新增、修改组件时,都需要在 ngModule 中进行更改,过程比较繁琐。

独立 APIs 创建组件

使用独立 APIs 创建组件、指令、管道:

创建组件、指令、管道时,在元数据中配置 standalone: true

在元数据中 imports 使用的其他独立组件、模块

比如创建按钮组件:其中 NgIf 、 NgClass 为 Angular 提供的独立组件

复制代码
@Component({
    selector: 'button',
    templateUrl: './button.component.html',
    changeDetection: ChangeDetectionStrategy.OnPush,
    standalone: true,
    imports: [NgIf, NgClass]
})
export class ButtonComponent {
}

回顾之前使用 NgModule 创建组件时,在模块定义中还会导出可用于其他模块/组件使用的组件列表,如果使用模块中多个组件,直接导入模块就行。使用独立组件如何设置一系列组件供其他模块/组件使用呢?

方式一:

定义一个 ngModule 作为多个独立组件的集合。此处需要注意的是:独立组件不可以再次在 ngModule 中声明。

复制代码
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';

import { ThyButtonComponent } from './button.component';


@NgModule({
    imports: [CommonModule, ThyButtonComponent],
    exports: [ButtonComponent]
})
export class ButtonModule {}

方式二:

定义一个常量数组,将多个独立组件导出。此处需要注意的是: 常量需要 as const ,为 Angular 编译器提供了正确编译所需的额外信息。

复制代码
export const BUTTON_COMPONENTS = [ThyButtonComponent] as const;

指令组合

如何使用

在指令支持独立指令后,独立指令还可以直接用于其他组件、指令,复用独立指令的逻辑。在指令或组件上使用 hostDirectives:[] ,将独立指令应用于组件。

如下示例, ThyFlex 为独立指令,指令有输入参数 thyDirection :

复制代码
@Directive({
    selector: '[flex]',
    standalone: true,
    host: {
        class: 'd-flex'
    }
})
export class Flex implements OnInit, OnChanges {
    @Input() direction: FlexDirection;
    
    constructor() {}
}

组件 flex :

复制代码
@Component({
    selector: 'flex',
    template: `<ng-content></ng-content>`,
    standalone: true,
    changeDetection: ChangeDetectionStrategy.OnPush,
    hostDirectives: [
        {
            directive: Flex,
            inputs: ['thyDirection: direction']
        }
    ],
    imports: [Flex]
})
export class FlexComponent {}

需要注意的是: 默认情况下,指令Flex 的输入、输出参数不会作为组件ThyFlexComponent 的公开 API,需要如上显示的定义。

使用:

javascript 复制代码
<flex thyDirection="xxx"></flex>

执行顺序

使用组合指令后,组件和宿主指令执行顺序:

宿主指令和直接在模板中使用的组件、指令会经历相同的生命周期。但是,宿主指令总是会在应用它们的组件或指令之前执行它们的构造函数、生命周期钩子和绑定。因此顺序为:

指令 Flex 实例化

组件 FlexComponent 实例化

指令 Flex 接收输入,执行 ngOninit

组件 FlexComponent 接收输入,执行 ngOninit

指令 Flex 应用宿主绑定

组件 FlexComponent 应用宿主绑定

依赖注入

使用了 hostDirectives 的组件或指令可以注入这些宿主指令的实例。

javascript 复制代码
@Component({
    selector: 'flex',
    template: `<ng-content></ng-content>`,
    standalone: true,
    changeDetection: ChangeDetectionStrategy.OnPush,
    hostDirectives: [
        {
            directive: Flex,
            inputs: ['direction']
        }
    ],
    imports: [Flex]
})
export class FlexComponent implements OnInit {
    private flexDirective = inject(Flex);

    constructor() {
        console.log('FlexComponent constructor');
    }

    ngOnInit(): void {
        console.log('FlexComponent ngOnInit');
    }
}

当把宿主指令应用于组件时,组件和宿主指令都可以定义提供者。如果带有 hostDirectives 的组件或指令以及这些宿主指令都提供相同的注入令牌,则带有 hostDirectives 的类定义的提供者会优先于宿主指令定义的提供者。

优势:

增强使用宿主指令的组件的功能;

增强代码的复用性

缺点:

过度使用宿主指令会影响应用程序的内存使用。

应用中使用

NgModule 启动应用

通过根模块启动应用,通过 bootstrapModule 启动,程序入口文件 main.ts 如下:

javascript 复制代码
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

AppModule 中,除了上边提到的 ngModule 中配置,还会通过 providers 设置全局的依赖,通过 bootstrap: [ AppComponent ] 设置根组件。启动后,将根组件插入到 index.html 页面中,进而构建组件树( bootstrap 中支持配置多个组件,一般情况跟组件只会设置一个组件)。

javascript 复制代码
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

独立应用启动

通过使用独立组件作为应用程序的根组件,可以在没有任何 ​ NgModule ​的情况下引导 Angular 应用程序。使用 ​ bootstrapApplication ​启动应用,配置根组件(根组件为独立组件):

main.ts:

javascript 复制代码
bootstrapApplication(AppComponent).catch(err => console.error(err));

AppComponent

javascript 复制代码
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet, TopBarComponent],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'demo-standalone';
}

回顾使用 ngModule 启动应用时,除了指定启动模块、启动组件,还需要配置全局服务和路由。在使用独立 APIs 启动应用如何配置呢?

依赖

可以在启动应用程序时,配置全局依赖。

javascript 复制代码
bootstrapApplication(AppComponent, {
  providers: [
    ProjectStore
  ]
})
  .catch((err) => console.error(err));

如果第三方库仅支持 ngModule 模式配置依赖,可以使用 importProvidersFrom 配置。

javascript 复制代码
import {LibraryModule} from 'ngmodule-based-library';

bootstrapApplication(PhotoAppComponent, {
  providers: [
    importProvidersFrom(
      RouterModule.forRoot(routes)
    )
  ]
});

此外,Angular 其他模块或者第三方库也支持了以 provide-* 的方法配置依赖。

provideRouter() // 可用于配置路由

provideHttpClient() // 可用于配置 HttpClient 服务

provideZoneChangeDetection() // 可用于配置 ngZone

provideAnimations() // 需要动画时,使用该方法

provideNoopAnimations()

provideServiceWorker()

provideServerRendering()

provideClientHydration()

...

在之前 NgModule 应用中,会根据在 @ngModule.providers 或者 @({providedIn: "..."}) 中配置依赖,根据配置创建模块注入器,在独立应用中,没有模块的概念,Angular 会创建环境注入器 environment injectors 。

一下场景会创建环境注入器:

@NgModule.providers ,通过 NgModule 引导的应用程序时

@({provideIn: "..."})

bootstrapApplication 独立应用启动时配置的 providers

Route 配置的 providers

除了在以上使用中 Angular 创建环境注入器,Angular 还支持 createEnvironmentInjector 创建。

路由

Angular 为支持独立应用,提供了 provideRouter 方法用于配置独立应用的路由。

javascript 复制代码
export const routes: Routes = [ 
    { path: 'products', component: ProductListComponent }
];
javascript 复制代码
bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    // importProvidersFrom(
    //   RouterModule.forRoot(routes)
    // ),
  ]
})
  .catch((err) => console.error(err));

惰性加载

之前,要惰性加载模块,Angular 提供了 loadChildren 方法,加载一组路由,使用如下:

javascript 复制代码
const routes: Routes = [
  {
    path: 'items',
    loadChildren: () => import('./items/items.module').then(m => m.ItemsModule)
  }
];

在独立应用中,同样支持通过 loadChildren 惰性加载一组路由

javascript 复制代码
// In the main application:
export const ROUTES: Route[] = [
  {path: 'admin', loadChildren: () => import('./admin/routes').then(mod => mod.ADMIN_ROUTES)},
  // ...
];
javascript 复制代码
// In admin/routes.ts:
export const ADMIN_ROUTES: Route[] = [
  {path: 'home', component: AdminHomeComponent},
  {path: 'users', component: AdminUsersComponent},
  // ...
];

此外还支持 loadComponent 加载独立组件。

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

简化使用:路由器会理解并使用 default 导出来的路由或者组件。

javascript 复制代码
// In the main application:
export const ROUTES: Route[] = [
  {path: 'admin', loadChildren: () => import('./admin/routes')},
  // ...
];
javascript 复制代码
// In admin/routes.ts:
export default [
  {path: 'home', component: AdminHomeComponent},
  {path: 'users', component: AdminUsersComponent},
  // ...
] as Route[];

工具

原有升级应用或类库升级使用独立组件

Angular (版本大于 15.2.0) 提供了完善的 Schematic 帮助原有应用升级转换为独立组件模式。

复制代码
ng generate @angular/core:standalone

按照下面列出的顺序运行迁移:

运行 ng g @angular/core:standalone 并选择 "Convert all components, directives and pipes to standalone"

运行 ng g @angular/core:standalone 并选择 "Remove unnecessary NgModule classes"

运行 ng g @angular/core:standalone 并选择 "Bootstrap the project using standalone APIs"

运行任何静态分析(lint)和格式检查,修复任何故障,并提交结果

生成独立启动应用

复制代码
ng new demo-standalone --standalone

生成独立组件

复制代码
ng g c button --standalone
相关推荐
崔庆才丨静觅14 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606115 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了15 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅15 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅16 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅16 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment16 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅16 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊16 小时前
jwt介绍
前端
爱敲代码的小鱼16 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax