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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 16:25:13