Angular中如何按行渲染多维数组中的动态组件?
按指定行渲染Angular动态组件方案
我有一个多维组件数组,需要渲染到网格中,数组结构如下:
[ [ { component: class TitleComponent, data: { title: "Invoice" }, row: 1 }, { component: class AddressComponent, data: { companyName: "Company Name", addressLineOne: "123 Street Name", addressLineTwo: "Area", addressLineThree: "City" }, row: 1 } ], [ { component: class DetailsComponent, data: { description: "lorem ipsum dolor sit amet" }, row: 2 } ] ]
预期按指定行渲染组件,比如TitleComponent渲染在第一行,DetailsComponent渲染在第二行,目标HTML结构如下:
<div *ngFor="let row of rows"> <div *ngFor="let component of row"> <ng-template gridBlock></ng-template> </div> </div>
核心需求是为每个行数组创建对应行容器,在该行内渲染组件,但之前用@ViewChildren渲染时,每次添加新动态组件,QueryList里的组件渲染顺序都会混乱,作为Angular新手,需要可行的实现方案。
实现步骤
1. 先对数据源按行分组(确保顺序)
先把原始组件数据按row字段分组并排序,保证行的顺序正确:
// 原始组件数据 componentsData = [ { component: TitleComponent, data: { title: "Invoice" }, row: 1 }, { component: AddressComponent, data: { companyName: "Company Name", addressLineOne: "123 Street Name", addressLineTwo: "Area", addressLineThree: "City" }, row: 1 }, { component: DetailsComponent, data: { description: "lorem ipsum dolor sit amet" }, row: 2 } ]; // 分组并排序 const groupedByRow = this.componentsData.reduce((acc, item) => { const rowKey = item.row; if (!acc[rowKey]) { acc[rowKey] = []; } acc[rowKey].push(item); return acc; }, {} as Record<number, typeof this.componentsData[0][]>); // 转成按行号排序的数组,绑定到模板的rows变量 this.rows = Object.keys(groupedByRow) .sort((a, b) => parseInt(a) - parseInt(b)) .map(key => groupedByRow[parseInt(key)]);
2. 自定义GridContainerDirective(获取视图容器)
这个指令用于获取动态组件的渲染容器:
import { Directive, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[gridBlock]' }) export class GridContainerDirective { constructor(public viewContainerRef: ViewContainerRef) { } }
3. 组件内动态渲染逻辑
在组件类中,通过@ViewChildren获取所有gridBlock指令,然后和分组后的组件数据一一对应渲染,避免顺序混乱:
import { Component, AfterViewInit, ViewChildren, QueryList, ComponentFactoryResolver } from '@angular/core'; import { GridContainerDirective } from './grid-container.directive'; import { TitleComponent } from './title.component'; import { AddressComponent } from './address.component'; import { DetailsComponent } from './details.component'; @Component({ selector: 'app-grid-renderer', template: ` <div *ngFor="let row of rows"> <div *ngFor="let component of row"> <ng-template gridBlock></ng-template> </div> </div> ` }) export class GridRendererComponent implements AfterViewInit { rows: any[] = []; componentsData = [/* 原始组件数据 */]; @ViewChildren(GridContainerDirective) gridBlocks!: QueryList<GridContainerDirective>; constructor(private componentFactoryResolver: ComponentFactoryResolver) { // 初始化时分组数据 const groupedByRow = this.componentsData.reduce((acc, item) => { const rowKey = item.row; if (!acc[rowKey]) { acc[rowKey] = []; } acc[rowKey].push(item); return acc; }, {} as Record<number, typeof this.componentsData[0][]>); this.rows = Object.keys(groupedByRow) .sort((a, b) => parseInt(a) - parseInt(b)) .map(key => groupedByRow[parseInt(key)]); } ngAfterViewInit(): void { this.renderComponents(); } // 统一渲染逻辑 private renderComponents() { // 把二维的rows转成一维数组,和gridBlocks的顺序一一对应 const componentItems = this.rows.flat(); this.gridBlocks.forEach((block, index) => { const item = componentItems[index]; if (!item) return; const container = block.viewContainerRef; container.clear(); // 清空原有内容 // 创建组件工厂并渲染 const componentFactory = this.componentFactoryResolver.resolveComponentFactory(item.component); const componentRef = container.createComponent(componentFactory); // 给动态组件传入数据 Object.assign(componentRef.instance, item.data); }); } // 动态添加组件的方法 addNewComponent(newComponent: {component: any, data: any, row: number}) { this.componentsData.push(newComponent); // 重新分组数据 const groupedByRow = this.componentsData.reduce((acc, item) => { const rowKey = item.row; if (!acc[rowKey]) { acc[rowKey] = []; } acc[rowKey].push(item); return acc; }, {} as Record<number, typeof this.componentsData[0][]>); this.rows = Object.keys(groupedByRow) .sort((a, b) => parseInt(a) - parseInt(b)) .map(key => groupedByRow[parseInt(key)]); // 等待视图更新后重新渲染 setTimeout(() => { this.renderComponents(); }); } }
4. 关键注意事项
- 必须在
ngAfterViewInit钩子中执行渲染,此时视图已初始化,@ViewChildren能获取到所有指令实例 - 动态添加组件后,需先更新数据源和
rows数组,等待Angular完成视图更新后(用setTimeout或ChangeDetectorRef触发变更检测)再重新渲染 - 依赖
rows.flat()后的一维数组和gridBlocks的索引一一对应,确保组件渲染顺序和行结构完全匹配
内容的提问来源于stack exchange,提问作者fourie_lean
相关产品推荐
相关产品推荐

