You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

遍历<ng-template>生成的HTML元素时classList未定义问题

问题描述

尝试遍历*ngFor生成的HTML元素,为其添加事件监听并执行类名的添加/移除操作,但遍历过程中无法正确获取实际元素,访问classList、innerHTML等属性时控制台抛出错误:ERROR TypeError: item.classList is undefined。

原模板代码

<div class="nav-container">
  <h2 class="fw-semibold text-center pt-3 logo">bytenotes</h2>
  <button class="newCat-btn">+ New Category</button>
  <nav #catItem class="navbar-container">
    <h3
      *ngFor="let category of categories; first as firstCat"
      class="catItem"
      [ngClass]="firstCat ? 'active' : ''"
    >
      {{ category }}
    </h3>
  </nav>
</div>

原组件代码

@Component({
  selector: 'app-nav',
  templateUrl: './nav.component.html',
  styleUrls: ['./nav.component.css', '../../styles.css'],
})
export class NavComponent implements AfterViewInit, OnInit {
  @ViewChild('catItem') catItem!: ElementRef<HTMLHeadingElement>;
  categories: Array<string> = [];
  activeCategory: string = '';

  constructor(private db: DatabaseService, private auth: Auth) {}

  ngOnInit(): void {
    let currUser = this.auth.currentUser?.uid || '';
    //Populate categories Array
    this.db.getCategories(currUser).then((data: any) => {
      let catArray = data.val();

      catArray.forEach((category: any) => {
        this.categories.push(category);
      });

    });
  }
  //Setting active class
  ngAfterViewInit(): void {
    let navItems = this.catItem.nativeElement.childNodes;
    console.log(navItems);

    navItems.forEach((item: any) => {

      //Sets Active category
      if (item.classList.contains('active'))
        this.activeCategory = item.innerText;

      item.addEventListener('click', () => {
        navItems.forEach((item: any) => {
          item.classList.remove('active');
        });
        item.classList.add('active');
        this.activeCategory = item.innerText;
        console.log(this.activeCategory);

        //Horizontal Scrolling only for smaller screen sizes
        if (window.screen.width < 1440)
          item.scrollIntoView({ behavior: 'smooth', inline: 'center' });
      });
    });
  }
}

问题分析与修复方案

核心原因

  1. childNodes包含非元素节点:childNodes会返回所有节点类型,包括换行、空格产生的空白文本节点,这些节点不是DOM元素,没有classList属性,导致报错。
  2. 异步数据渲染时机问题:categories是异步获取的,ngAfterViewInit执行时数据可能还未加载完成,此时DOM还未渲染出*ngFor的元素。

修复方式一:改进DOM操作逻辑

import { ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'app-nav',
  templateUrl: './nav.component.html',
  styleUrls: ['./nav.component.css', '../../styles.css'],
})
export class NavComponent implements OnInit, AfterViewChecked {
  @ViewChild('catItem') catItem!: ElementRef<HTMLElement>;
  categories: Array<string> = [];
  activeCategory: string = '';
  private hasBoundEvents = false; // 标记是否已绑定事件,避免重复绑定

  constructor(
    private db: DatabaseService, 
    private auth: Auth,
    private cdr: ChangeDetectorRef // 注入变更检测器
  ) {}

  ngOnInit(): void {
    const currUser = this.auth.currentUser?.uid || '';
    this.db.getCategories(currUser).then((data: any) => {
      const catArray = data.val();
      this.categories = catArray;
    });
  }

  ngAfterViewChecked(): void {
    // 数据未加载或已绑定过事件则跳过
    if (this.categories.length === 0 || this.hasBoundEvents) return;

    const navItems = this.catItem.nativeElement.children;
    if (navItems.length === 0) return;

    this.hasBoundEvents = true;
    // 转换为数组后遍历(children是HTMLCollection,需转换为数组才能用forEach)
    Array.from(navItems).forEach((item: HTMLElement) => {
      if (item.classList.contains('active')) {
        this.activeCategory = item.innerText;
      }

      item.addEventListener('click', () => {
        Array.from(navItems).forEach(el => el.classList.remove('active'));
        item.classList.add('active');
        this.activeCategory = item.innerText;

        if (window.screen.width < 1440) {
          item.scrollIntoView({ behavior: 'smooth', inline: 'center' });
        }
      });
    });

    // 手动触发变更检测
    this.cdr.detectChanges();
  }
}

修复方式二:推荐使用Angular数据驱动方案(避免直接操作DOM)

Angular核心是数据驱动视图,直接操作DOM易导致视图与数据不一致,推荐用绑定语法实现需求:

模板修改

<div class="nav-container">
  <h2 class="fw-semibold text-center pt-3 logo">bytenotes</h2>
  <button class="newCat-btn">+ New Category</button>
  <nav class="navbar-container">
    <h3
      *ngFor="let category of categories"
      class="catItem"
      [class.active]="activeCategory === category"
      (click)="handleCategoryClick(category, $event)"
    >
      {{ category }}
    </h3>
  </nav>
</div>

组件修改

@Component({
  selector: 'app-nav',
  templateUrl: './nav.component.html',
  styleUrls: ['./nav.component.css', '../../styles.css'],
})
export class NavComponent implements OnInit {
  categories: Array<string> = [];
  activeCategory: string = '';

  constructor(private db: DatabaseService, private auth: Auth) {}

  ngOnInit(): void {
    const currUser = this.auth.currentUser?.uid || '';
    this.db.getCategories(currUser).then((data: any) => {
      const catArray = data.val();
      this.categories = catArray;
      // 默认激活第一个分类
      if (this.categories.length > 0) {
        this.activeCategory = this.categories[0];
      }
    });
  }

  handleCategoryClick(category: string, event: MouseEvent): void {
    this.activeCategory = category;
    // 滚动逻辑
    if (window.screen.width < 1440) {
      const target = event.target as HTMLElement;
      target.scrollIntoView({ behavior: 'smooth', inline: 'center' });
    }
  }
}

内容的提问来源于stack exchange,提问作者Ryurcia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 17:46:04