You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 21:45:28