如何在Angular的多个页面间共享一个开销高昂的组件?
在Angular多页面中复用同一个组件实例(保持状态)
我有一个Angular应用,需要在多个不同页面中共享一个表格组件。该表格的数据获取操作开销高昂,因此希望在不同页面复用同一个表格实例。此外,表格在每个页面中的布局位置不同,无法将其作为通用布局的一部分。
页面示例
full-view-page.ts
@Component({ selector: `full-page`, template: ` <h1>Accounts Table</h1> <expensive-table></expensive-table> ` })
accounts-page.ts
@Component({ selector: 'accounts-page', template: ` <h1>Accounts</h1> <expensive-table></expensive-table> <form *ngIf="account"> // Form content here </form> `, styles: [/* Some styles here... */] })
charts-page.ts
@Component({ selector: 'charts-page', template: ` <h1>Charts</h1> <analysis-charts></analysis-charts> <expensive-table></expensive-table> <recommendation></recommendation> `, styles: [/* Some styles here... */] })
当前每个页面都会加载独立的表格实例,理想状态是Angular仅加载一次expensive-table组件,切换页面时复用该实例,保持组件状态(当前搜索、滚动位置、筛选条件)始终一致。
解决方案:使用Angular CDK的Portal机制
通过Portal可以将组件动态挂载到不同DOM位置,同时保留组件实例和状态。
1. 安装Angular CDK
npm install @angular/cdk
2. 创建共享表格服务
这个服务负责创建并维护唯一的表格实例,提供挂载/卸载方法:
import { Injectable, Injector } from '@angular/core'; import { ComponentPortal, PortalOutlet } from '@angular/cdk/portal'; import { ExpensiveTableComponent } from './expensive-table.component'; @Injectable({ providedIn: 'root' }) export class TableSharedService { private tablePortal: ComponentPortal<ExpensiveTableComponent>; private currentOutlet: PortalOutlet | null = null; constructor(private injector: Injector) { // 仅创建一次表格的Portal实例 this.tablePortal = new ComponentPortal(ExpensiveTableComponent, null, this.injector); } // 将表格挂载到指定容器 attachTable(outlet: PortalOutlet): void { if (this.currentOutlet) { this.currentOutlet.detach(); } this.currentOutlet = outlet; this.currentOutlet.attach(this.tablePortal); } // 从当前容器卸载表格 detachTable(): void { if (this.currentOutlet) { this.currentOutlet.detach(); this.currentOutlet = null; } } }
3. 修改页面组件,使用Portal容器
将每个页面中的<expensive-table>替换为cdk-portal-outlet,并在组件类中调用服务挂载表格:
full-view-page.ts 改造后
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; import { CdkPortalOutlet } from '@angular/cdk/portal'; import { TableSharedService } from './table-shared.service'; @Component({ selector: `full-page`, template: ` <h1>Accounts Table</h1> <cdk-portal-outlet #tableOutlet></cdk-portal-outlet> ` }) export class FullViewPageComponent implements OnInit, OnDestroy { @ViewChild('tableOutlet') tableOutlet!: CdkPortalOutlet; constructor(private tableService: TableSharedService) {} ngOnInit(): void { this.tableService.attachTable(this.tableOutlet); } ngOnDestroy(): void { this.tableService.detachTable(); } }
accounts-page.ts 改造后
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; import { CdkPortalOutlet } from '@angular/cdk/portal'; import { TableSharedService } from './table-shared.service'; @Component({ selector: 'accounts-page', template: ` <h1>Accounts</h1> <cdk-portal-outlet #tableOutlet></cdk-portal-outlet> <form *ngIf="account"> // Form content here </form> `, styles: [/* Some styles here... */] }) export class AccountsPageComponent implements OnInit, OnDestroy { @ViewChild('tableOutlet') tableOutlet!: CdkPortalOutlet; account: any; constructor(private tableService: TableSharedService) {} ngOnInit(): void { this.tableService.attachTable(this.tableOutlet); } ngOnDestroy(): void { this.tableService.detachTable(); } }
charts-page.ts 改造后
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; import { CdkPortalOutlet } from '@angular/cdk/portal'; import { TableSharedService } from './table-shared.service'; @Component({ selector: 'charts-page', template: ` <h1>Charts</h1> <analysis-charts></analysis-charts> <cdk-portal-outlet #tableOutlet></cdk-portal-outlet> <recommendation></recommendation> `, styles: [/* Some styles here... */] }) export class ChartsPageComponent implements OnInit, OnDestroy { @ViewChild('tableOutlet') tableOutlet!: CdkPortalOutlet; constructor(private tableService: TableSharedService) {} ngOnInit(): void { this.tableService.attachTable(this.tableOutlet); } ngOnDestroy(): void { this.tableService.detachTable(); } }
4. 配置模块
在NgModule中导入PortalModule并声明表格组件:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { PortalModule } from '@angular/cdk/portal'; import { ExpensiveTableComponent } from './expensive-table.component'; // 导入其他页面组件 @NgModule({ imports: [BrowserModule, PortalModule], declarations: [ExpensiveTableComponent, FullViewPageComponent, AccountsPageComponent, ChartsPageComponent], bootstrap: [/* 根组件 */] }) export class AppModule {}
原理说明
ComponentPortal封装了expensive-table的实例,仅在服务初始化时创建一次。CdkPortalOutlet作为可动态挂载的容器,每个页面的容器位置不同,但共享同一个Portal实例。- 切换页面时,服务将表格从旧容器移至新容器,组件实例和状态(搜索、滚动、筛选)完全保留。
内容的提问来源于stack exchange,提问作者Ramneek Singh
相关产品推荐
相关产品推荐

