Angular 14多次复用组件时嵌套数组计数指令的问题解决
解决方案:Angular嵌套数组计数指令的多组件隔离问题
问题根源
当前指令的static nextIndex是全局静态变量,所有NestedArrayCounterDirective实例共享同一计数器。当FormComponent被多次复用时,后渲染的组件会继承前一个组件的计数终点,导致formGroupName索引越界,触发数组访问错误。
方案1:用Map(对应C# Dictionary)实现分组计数
通过静态Map存储不同组件实例的独立计数器,用唯一组ID作为键,实现计数隔离,完全贴合你提到的字典思路。
重构后的指令代码
import { AfterViewInit, Directive, Input, OnDestroy } from '@angular/core'; @Directive({ selector: '[rhsNestedArrayCounter]', exportAs: 'rhsNestedArrayCounter' }) export class NestedArrayCounterDirective implements AfterViewInit, OnDestroy { // 静态Map:键为组件唯一组ID,值为该组的当前计数 static counters = new Map<string, number>(); // 输入组ID,区分不同组件实例的计数器 @Input() rhsNestedArrayCounter: string | number = 'default'; @Input() appCounter: number | string = ''; index: number; private groupId: string; constructor() { // 将输入的组ID转为字符串作为Map键 this.groupId = String(this.rhsNestedArrayCounter); // 初始化当前组的计数器(默认从-1开始,自增后为0) if (!NestedArrayCounterDirective.counters.has(this.groupId)) { NestedArrayCounterDirective.counters.set(this.groupId, -1); } // 自增并赋值给当前实例的index this.index = ++NestedArrayCounterDirective.counters.get(this.groupId)!; } ngAfterViewInit(): void { if (typeof this.appCounter !== 'string') { // 重置当前组的计数器值 NestedArrayCounterDirective.counters.set(this.groupId, this.appCounter); } } ngOnDestroy(): void { // 组件销毁时移除对应组的计数器,避免内存泄漏 NestedArrayCounterDirective.counters.delete(this.groupId); } }
修改HTML使用方式
给每个FormComponent的根容器传递唯一组ID(可在组件内生成随机标识):
<form [formGroup]="detailForm" class="form-container"> <!-- 用组件的唯一标识作为组ID --> <ng-container [rhsNestedArrayCounter]="componentId"> <!-- 原有嵌套结构保持不变 --> <div *ngFor="let section of sections; let sectionIndex = index"> <div class="section"> <div class="section-content"> <h3>{{ section.title }}</h3> <div formArrayName="responses"> <div *ngFor="let questionWithAnswer of section.questionsWithAnswers; let questionIndex = index"> <div class="question"> <div rhsNestedArrayCounter #counter="rhsNestedArrayCounter" class="question-content" [formGroupName]="counter.index"> <!-- 原有内容保持不变 --> </div> </div> </div> </div> </div> </div> </div> </ng-container> </form>
在FormComponent中生成唯一组ID
// FormComponent.ts import { Component } from '@angular/core'; @Component({ selector: 'app-form', templateUrl: './form.component.html', styleUrls: ['./form.component.css'] }) export class FormComponent { // 生成随机唯一标识,作为计数器的组ID componentId = `form-${Math.random().toString(36).slice(2, 11)}`; // 其他原有代码... }
方案2:父子指令隔离(更贴合Angular视图层级)
利用Angular的依赖注入层级,让根容器指令维护独立计数器,子指令从当前组件内的根容器获取计数,无需手动传递组ID。
根容器指令(CounterGroupDirective)
import { Directive, Input, OnInit, OnDestroy } from '@angular/core'; @Directive({ selector: '[rhsCounterGroup]', exportAs: 'rhsCounterGroup' }) export class CounterGroupDirective implements OnInit, OnDestroy { currentIndex = -1; @Input() initialIndex = 0; ngOnInit() { // 初始化计数起点(自增后匹配初始值) this.currentIndex = this.initialIndex - 1; } // 提供自增方法给子指令调用 nextIndex(): number { return ++this.currentIndex; } // 重置计数器 reset(index: number) { this.currentIndex = index; } ngOnDestroy(): void { // 无静态变量,销毁时自动回收资源 } }
子计数指令(NestedArrayCounterDirective)
import { Directive, Input, OnDestroy, Optional, SkipSelf } from '@angular/core'; import { CounterGroupDirective } from './counter-group.directive'; @Directive({ selector: '[rhsNestedArrayCounter]', exportAs: 'rhsNestedArrayCounter' }) export class NestedArrayCounterDirective implements OnDestroy { index: number; @Input() appCounter: number | string = ''; constructor( // 注入当前组件内的根容器指令,自动隔离计数 @Optional() @SkipSelf() private counterGroup: CounterGroupDirective ) { if (!this.counterGroup) { throw new Error('rhsNestedArrayCounter必须在rhsCounterGroup内部使用'); } this.index = this.counterGroup.nextIndex(); } ngAfterViewInit(): void { if (typeof this.appCounter !== 'string') { this.counterGroup.reset(this.appCounter); } } ngOnDestroy(): void { // 无需额外操作,父级销毁时自动清理 } }
修改HTML使用方式
<form [formGroup]="detailForm" class="form-container"> <!-- 根容器用rhsCounterGroup指定初始计数 --> <ng-container rhsCounterGroup [initialIndex]="0"> <!-- 原有嵌套结构保持不变 --> <div *ngFor="let section of sections; let sectionIndex = index"> <div class="section"> <div class="section-content"> <h3>{{ section.title }}</h3> <div formArrayName="responses"> <div *ngFor="let questionWithAnswer of section.questionsWithAnswers; let questionIndex = index"> <div class="question"> <div rhsNestedArrayCounter #counter="rhsNestedArrayCounter" class="question-content" [formGroupName]="counter.index"> <!-- 原有内容保持不变 --> </div> </div> </div> </div> </div> </div> </div> </ng-container> </form>
方案对比
- 方案1:灵活适配任意结构,适合需要手动控制分组场景,对应你熟悉的C# Dictionary思路。
- 方案2:完全贴合Angular的组件层级依赖,无需手动维护组ID,代码更简洁。
内容的提问来源于stack exchange,提问作者Marco Nuñez
相关产品推荐
相关产品推荐

