Angular12 CDK Portal中如何访问内部元素动态添加CSS类
问题根因
@ViewChild 只能查询当前组件视图初始化阶段就存在的元素。ng-template 内的内容是惰性渲染的,只有被CDK Portal实例化并挂载到DOM后,内部的#newFormField才会真实存在。直接在宿主组件里写@ViewChild('newFormField')拿不到是必然的:一是模板没实例化时元素根本不存在,二是Portal动态创建的模板视图不属于宿主组件的初始视图树,普通的@ViewChild查询不会覆盖到这部分内容。
推荐实现:通过模板绑定控制类,无需直接操作DOM
这是最符合Angular设计规范的写法,完全不需要手动获取DOM引用,靠Angular自带的变更检测就能自动更新类名:
- 调整模板,给
ng-template声明上下文参数,绑定类名到上下文状态:
<ng-template #templatePortalContent let-isEditing="isEditing"> <div class="editable" [class.editingActive]="isEditing"> stuff </div> </ng-template>
- 实例化
TemplatePortal时传入上下文对象,后续直接修改上下文状态就能控制类的增删:
import { TemplatePortal } from '@angular/cdk/portal'; @ViewChild('templatePortalContent') templatePortalContent: TemplateRef<any>; // 实例化Portal,传入初始上下文 const formPortal = new TemplatePortal( this.templatePortalContent, this.viewContainerRef, { isEditing: false } ); // 挂载到目标PortalOutlet this.portalOutlet.attach(formPortal); // 需要添加editingActive类时,直接修改上下文值即可,不需要操作DOM formPortal.context.isEditing = true;
备选方案:必须获取DOM引用时的实现
如果业务逻辑确实需要拿到这个元素的ElementRef做其他操作,必须等Portal挂载完成、变更检测执行完之后再查询,不要在宿主组件的ngAfterViewInit等生命周期里直接查,那时候模板还没渲染:
- 挂载完成后直接从返回的视图根节点里找目标元素:
const attachedView = this.portalOutlet.attach(formPortal); // 先触发变更检测,确保节点渲染完成 attachedView.detectChanges(); // 从根节点里找到目标div const newFormField = attachedView.rootNodes.find( node => node.classList?.contains('editable') ) as HTMLElement; newFormField.classList.add('editingActive');
- 更稳妥的写法是用标记指令拿引用,避免靠类名匹配出错:
先写一个极简的标记指令:
@Directive({ selector: '[formFieldRef]' }) export class FormFieldRefDirective { constructor(public el: ElementRef<HTMLElement>) {} }
在模板里给目标元素加上这个指令:
<ng-template #templatePortalContent> <div class="editable" formFieldRef> stuff </div> </ng-template>
Portal挂载后直接从视图注入器里拿指令实例,就能拿到准确的元素引用:
const attachedView = this.portalOutlet.attach(formPortal); attachedView.detectChanges(); const fieldEl = attachedView.injector.get(FormFieldRefDirective).el.nativeElement; fieldEl.classList.add('editingActive');
注意事项
所有针对Portal动态插入模板内部元素的操作,都要放在attach方法执行完成、手动触发一次变更检测之后再做,宿主组件的初始化生命周期执行时,这部分动态内容还没完成渲染,此时查询必然返回undefined。
内容的提问来源于stack exchange,提问作者Steve
相关产品推荐
相关产品推荐

