Angular 如何向通用组件传递/组合自定义组件实现代码复用?
该需求完全可以在Angular框架下实现,以下是你调研的三种方案的适用场景、优劣势对比,以及最优选择建议:
方案对比
ng-content 内容投影
适用场景:Tabs项固定、提前知晓所有Tab内容、不需要动态生成Tab的场景
优势:写法最简单,自定义组件的inputs、outputs、事件、自定义指令都可以直接正常绑定,不需要额外处理逻辑
劣势:不支持动态生成Tab(比如Tab项是接口返回的动态列表需要循环渲染的场景),所有Tab内容会在Tabs组件初始化时同时渲染,Tab数量多的场景下性能较差
调用示例:<!-- 父组件调用 --> <app-tabs> <app-tab label="表单"> <!-- 自定义组件直接写在内层,绑定逻辑不需要修改 --> <app-custom-form [formData]="formData" (submit)="onSubmit($event)"></app-custom-form> </app-tab> <app-tab label="表格"> <app-data-grid [list]="gridList" (rowClick)="onRowClick($event)"></app-data-grid> </app-tab> </app-tabs>Tabs组件内部直接用
<ng-content></ng-content>接收投影内容即可。ng-template + TemplateRef 模板传入
适用场景:需要动态生成Tab、Tab项数量不固定、需要按需渲染Tab内容的场景,是绝大多数通用Tabs组件的首选方案
优势:支持动态循环生成Tab,默认仅渲染激活态的Tab内容,性能表现更好;自定义组件的所有绑定逻辑都在父组件侧完成,不需要在Tabs组件内做额外的输入输出透传,还支持自定义上下文传参
劣势:写法比ng-content稍复杂,需要配合ngTemplateOutlet使用
实现示例:// Tabs组件ts定义输入项 @Input() tabs: Array<{label: string, template: TemplateRef<any>, context?: any}> @Input() activeIndex: number = 0<!-- Tabs组件内部渲染逻辑 --> <div class="tab-header"> <div *ngFor="let tab of tabs; let i = index" [class.active]="activeIndex === i" (click)="activeIndex = i"> {{tab.label}} </div> </div> <div class="tab-content"> <ng-container *ngFor="let tab of tabs; let i = index"> <ng-container *ngIf="activeIndex === i"> <ng-template [ngTemplateOutlet]="tab.template" [ngTemplateOutletContext]="tab.context"></ng-template> </ng-container> </ng-container> </div><!-- 父组件调用 --> <app-tabs [tabs]="tabList" [activeIndex]="0"></app-tabs> <ng-template #formTab let-data> <app-custom-form [formData]="data" (submit)="onSubmit($event)"></app-custom-form> </ng-template> <ng-template #gridTab let-list> <app-data-grid [list]="list" (rowClick)="onRowClick($event)"></app-data-grid> </ng-template>// 父组件ts获取模板引用 @ViewChild('formTab') formTab: TemplateRef<any>; @ViewChild('gridTab') gridTab: TemplateRef<any>; tabList = []; ngAfterViewInit() { this.tabList = [ {label: '表单', template: this.formTab, context: {$implicit: this.formData}}, {label: '表格', template: this.gridTab, context: {$implicit: this.gridList}} ] }动态组件(ComponentFactoryResolver/ViewContainerRef)
适用场景:Tab内容完全动态、需要在运行时才确定渲染哪个组件(比如组件类型由后端接口返回)的极端场景
优势:完全动态,不需要提前在父组件编写ng-template模板
劣势:实现复杂度高,需要手动处理组件的inputs、outputs透传,还要处理依赖注入相关问题,维护成本高
最优方案选择建议
- 如果你的Tabs组件仅用于固定Tab项的业务场景,优先选ng-content,写法最简单,维护成本最低
- 如果你的Tabs需要支持动态生成Tab、或者需要懒加载Tab内容提升性能,优先选ng-template + TemplateRef,这是Angular生态下主流组件库(Angular Material、NG-ZORRO等)的Tabs组件标准实现方案,平衡了灵活性和维护成本
- 只有当你完全不知道Tab要渲染什么内容、组件类型需要运行时动态获取的特殊场景,才选择动态组件方案
内容的提问来源于stack exchange,提问作者cuznerdexter

