遍历<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' }); }); }); } }
问题分析与修复方案
核心原因
childNodes包含非元素节点:childNodes会返回所有节点类型,包括换行、空格产生的空白文本节点,这些节点不是DOM元素,没有classList属性,导致报错。- 异步数据渲染时机问题:
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
相关产品推荐
相关产品推荐

