Angular中如何实现ng-content的动态选择器?
Angular动态切换ng-content投影内容的解决方案
Angular的ng-content是编译时投影机制,select属性的值在组件编译阶段就会被解析并完成内容分配,运行时动态修改select的值无法触发重新投影,所以你尝试的<ng-content select="[id={{activeId}}]"></ng-content>写法不会生效。
以下是两种可行的实现方案:
方案一:使用ng-template和动态模板渲染
这种方式通过将投影内容封装为ng-template,在子组件中动态选择并渲染对应模板,是更符合Angular设计模式的方案。
父组件用法修改
<app-sub #sub> <!-- 用ng-template包裹内容,通过模板变量标识 --> <ng-template #fullscreen> <div> <p>Fullscreen</p> </div> </ng-template> <ng-template #test> <div> <p>Some other content</p> </div> </ng-template> </app-sub> <button (click)="sub.toggle('test')">Switch to test</button> <button (click)="sub.toggle()">Switch to fullscreen</button>
子组件实现
import { Component, ViewChildren, QueryList, TemplateRef } from '@angular/core'; @Component({ selector: 'app-sub', template: ` @if (activeId) { <p>In Active with: {{activeId}}</p> <ng-container *ngTemplateOutlet="activeTemplate"></ng-container> } @else { <p>In Non Active</p> <ng-container *ngTemplateOutlet="fullscreenTemplate"></ng-container> } `, }) export class SubComponent { protected activeId?: string; protected activeTemplate?: TemplateRef<any>; protected fullscreenTemplate?: TemplateRef<any>; @ViewChildren(TemplateRef) private templates!: QueryList<TemplateRef<any>>; ngAfterViewInit() { // 初始化时找到fullscreen模板 this.fullscreenTemplate = this.getTemplateByKey('fullscreen'); } public toggle(id?: string) { this.activeId = id; this.activeTemplate = id ? this.getTemplateByKey(id) : undefined; } private getTemplateByKey(key: string): TemplateRef<any> | undefined { return this.templates.find(tpl => { // 通过模板的本地变量名匹配 const tplName = (tpl as any)._declarationTContainer?.localNames[0]; return tplName === key; }); } }
方案二:收集投影内容并动态渲染
这种方式直接收集所有投影的DOM元素,通过克隆元素的方式动态切换显示内容,父组件用法无需修改。
子组件实现
import { Component, ContentChildren, QueryList, ElementRef, ViewChild, ViewContainerRef, AfterContentInit } from '@angular/core'; @Component({ selector: 'app-sub', template: ` <p>{{activeId ? 'In Active with: ' + activeId : 'In Non Active'}}</p> <ng-container #contentContainer></ng-container> `, }) export class SubComponent implements AfterContentInit { protected activeId?: string; private contentMap = new Map<string, ElementRef>(); @ContentChildren(ElementRef, { descendants: false }) private contentElements!: QueryList<ElementRef>; @ViewChild('contentContainer', { read: ViewContainerRef }) private contentContainer!: ViewContainerRef; ngAfterContentInit() { // 建立内容标识与元素的映射 this.contentElements.forEach(elRef => { const el = elRef.nativeElement; if (el.hasAttribute('fullscreen')) { this.contentMap.set('fullscreen', elRef); } else if (el.id) { this.contentMap.set(el.id, elRef); } }); // 默认渲染fullscreen内容 this.renderContent('fullscreen'); } public toggle(id?: string) { this.activeId = id; this.renderContent(id || 'fullscreen'); } private renderContent(key: string) { // 清空容器 this.contentContainer.clear(); const elRef = this.contentMap.get(key); if (elRef) { // 克隆DOM元素(原始投影内容只能被使用一次) const clonedEl = elRef.nativeElement.cloneNode(true); this.contentContainer.element.nativeElement.appendChild(clonedEl); } } }
父组件用法保持不变
<app-sub #sub> <div fullscreen> <p>Fullscreen</p> </div> <div id="test"> <p>Some other content</p> </div> </app-sub> <button (click)="sub.toggle('test')">Switch to test</button> <button (click)="sub.toggle()">Switch to fullscreen</button>
内容的提问来源于stack exchange,提问作者Link
相关产品推荐
相关产品推荐

