游乐游手机版
首页/前端开发/文章详情

Angular获取ngIf条件渲染DOM元素示例

时间:2026-06-17 06:46
在Angular开发中,通过@ViewChild获取*ngIf渲染的DOM元素时需将static显式设为false,否则会返回undefined。利用AngularCDK的拖拽功能实现跨组件元素互拖,需通过全局store与RxJS动态更新可拖拽列表,并在拖拽回调中更新父子节点关系。

Angular获取普通Dom元素的方法

在Angular开发中,获取DOM元素是高频操作,但一个容易被忽略的细节就能卡住开发者很长时间。通常我们通过模板变量名(#greet)配合@ViewChild获取元素,这种方式在绝大多数场景下都很顺手。然而一旦遇上由*ngIf控制显示的元素,问题立刻暴露——获取到的往往是undefined。下面查看一段经典代码:

import { Component, ViewChild, AfterViewInit } from '@angular/core';
@Component({
  selector: 'my-app',
  template: `
    

Welcome to Angular World

Hello {{ name }}

`, }) export class AppComponent { name: string = 'Semlinker'; @ViewChild('greet') greetDiv: ElementRef; ngAfterViewInit() { console.log(this.greetDiv.nativeElement); } }

这段代码在普通元素上运行良好,但一旦换成*ngIf包裹的结构,就会扑空。具体场景如下:

Angular获取ngIf渲染的Dom元素示例

将static改成false 获取

解决方法其实很简单:在@ViewChild的配置选项中,将static显式设置为false。Angular默认的static值就是false,但当你向@ViewChild传递第二个参数时,某些版本下该值可能会被误配置为true。明确指定static: false能让查询行为符合预期——等到结构渲染完成后再去查找。改造后的代码如下:

@ViewChild('dropList', { read: CdkDropList, static: false }) dropList: CdkDropList;
ngAfterViewInit(): void {
    if (this.dropList) {
      console.log(this.dropList)
    }
  }

使用这一方案,就能顺利获取到*ngIf渲染后的cdkDropList实例。顺便提一下,上面这段代码源自一个实际功能:buttonGroup内的按钮与某个列表里的按钮可以互相拖拽,底层正是利用Angular自带的cdk/drag-drop实现。

import { CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';

自己实现的思路

官方文档中的demo比较简单,并未涉及跨组件的拖拽整合。这里分享实现时的核心思路。

首先,将所有需要拖拽的元素都加入cdkDropList。在A组件和B组件各自初始化时,获取各自的DOM元素,并注册到一个全局store中,每个元素附带一个唯一的componentId

A、B组件通过cdkDropListConnectedTo属性控制跨组件拖拽的目标范围,该属性绑定到一个动态数组_connectableDropLists。在页面初始化时,利用RxJS订阅特定componentId的变化,一旦store中有新的拖拽列表注册进来,就更新_connectableDropLists。关键代码片段如下:

const parentId = this.storeService.getProperty(this.pageId, this.componentId, 'parentId');
this.dragDropService.getDragListsAsync(this.pageId, parentId.value)
      .pipe(takeUntil(this.destroy))
      .subscribe(dropLists => {
        this._connectableDropLists = dropLists || [];
      });
this.storeService.getPropertyAsync(this.pageId, this.componentId, 'children')
      .pipe(takeUntil(this.destroy)).subscribe(result => {
        if (!result || result.length === 0) {
          this._children = [];
          this._dragData = [];
          this.changeRef.markForCheck();
        } else {
          const dropbuttonArray = result.filter((item) => {
            const itemType = this.storeService.getProperty(this.pageId, item, 'componentType');
            if (itemType === AdmComponentType.DropdownButton) return item;
          });
          if (dropbuttonArray.length > 0) {
            this._connectableDropLists = [];
            dropbuttonArray.forEach(comId => {
              this.dragDropService.getDragListsAsync(this.pageId, comId)
                .pipe(takeUntil(this.destroy))
                .subscribe(dropLists => {
                  this._connectableDropLists.push(...dropLists);
                });
            });
          }
        }
      });

由于A组件是B组件的父级,因此需要通过当前组件id获取父级id,再进一步获取可拖拽的元素列表。

通过cdkDragData 把拖拽的元素的value,id等值带上

在模板中通过(cdkDropListDropped)="drop($event)"注册拖拽结束的回调。回调里需要处理数据更新——本质上是删除旧父级下的子节点,再将当前组件添加到新父级下面,同时更新parentId。另外,buttonGroup内部的按钮之间也允许互相拖拽,所以需要加一层判断做特殊处理。

drop(event: CdkDragDrop) {
    if (event.previousContainer != event.container) {
      const { eventData } = event.item.data;
      const componentId = eventData[event.previousIndex];
      const oldParentId = this.storeService.getProperty(this.pageId, componentId, 'parentId', false)?.value;
      // delete oldParent children
      const oldParent = this.storeService.getProperties(this.pageId, oldParentId);
      const index = oldParent.children.indexOf(componentId);
      oldParent.children.splice(index, 1);
      // add newParent children
      const oldChildren = this.itemDatas.map(x => x.id.value);
      oldChildren.splice(event.currentIndex, 0, componentId);
      this.storeService.setProperty(this.pageId, componentId, 'parentId', { value: this.componentId }, [[this.pageId, componentId]]);
      this.storeService.setProperty(this.pageId, oldParentId, 'children', oldParent.children, [[this.pageId, oldParentId]]);
      this.storeService.setProperty(this.pageId, this.componentId, 'children', oldChildren);
      this.changeDetector.markForCheck();
      return;
    }
    moveItemInArray(this.itemDatas, event.previousIndex, event.currentIndex);
    const children = this.itemDatas.map(x => x.id.value);
    this.storeService.setProperty(this.pageId, this.componentId, 'children', children);
  }

这样一来,子组件和父组件内部的元素就能互相拖拽,整个交互流程也就顺畅起来。

来源:https://www.jb51.net/article/284189.htm
上一篇Angular中延迟加载基本原理详解与使用实践指南 下一篇Ext JS 4实现带星期的日期选择控件实战二
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Vue应用中异步更新性能问题的优化策略详解
前端开发 · 2026-07-03

Vue应用中异步更新性能问题的优化策略详解

先来看一个令许多开发者感到困惑的场景:明明修改了数据,DOM 却“毫无反应”,无法获取最新的高度,也无法计算正确的坐标。这并非 Vue 的缺陷,反而是它精心设计的性能优化策略。核心在于——你需要学会与它“异步更新”的特性协作,而非硬碰硬。 所谓的“异步更新性能问题”,本质上是一种认知偏差。Vue 的

如何避免原型对象挂载大体积动态数组内存污染
前端开发 · 2026-07-03

如何避免原型对象挂载大体积动态数组内存污染

原型链上的大数组:一个隐蔽的内存冲击波 先给个核心判断:直接在原型对象上挂载一个大体积动态数组,这既不是传统意义上的内存“污染”,也不是安全漏洞那种“污染”,而是一种相当隐蔽但后果严重的内存管理失当。它会导致所有实例共享同一份数据,而且正因为生命周期跟整个原型链绑定得太紧,垃圾回收器(GC)根本看不

利用堆栈信息精准定位显式绑定错误对象致未定义异常
前端开发 · 2026-07-03

利用堆栈信息精准定位显式绑定错误对象致未定义异常

深入追踪:显式绑定传错对象引发的未定义异常 说实话,这类问题在JavaScript开发中相当常见——显式绑定传错了对象,然后方法执行时静默失败、访问undefined、或者抛出TypeError。但真正的难点不在于“报了什么错”,而在于“到底是哪个对象被绑错了”。要解决它,需要跳出堆栈的表层报错信息

ES模块中默认导出和具名导出的执行上下文
前端开发 · 2026-07-03

ES模块中默认导出和具名导出的执行上下文

export default 与具名导出在 ES Module 中的行为机制截然不同,核心差异不在于“值如何传递”,而在于绑定如何建立以及导入时如何使用。先给出总结性结论,再逐一详细拆解。 export default 是一种语法糖,而非真正的变量声明 这种设计容易引起误解。实际上,export d

详解HTML中iframe标签loading=lazy属性实现嵌入内容懒加载方法
前端开发 · 2026-07-03

详解HTML中iframe标签loading=lazy属性实现嵌入内容懒加载方法

先聊聊 loading= "lazy " 这个属性——它本意是让 iframe 实现延迟加载,但实际落地时常常“失效”。这并非程序漏洞,而是浏览器内置的防御机制:只有所有条件同时触发,它才会真正推迟资源请求。比如 src 必须是跨域地址(类似 https: widget example com emb