如何识别 FormArray 中的哪个项目发出了 valueChanges 事件?

How to identify which item in FormArray emitted valueChanges event?

Angular 中,有没有办法识别 dynamicFormArray 中的哪个 FormGroup/FormControl 发出了valueChanges 事件?

我的 FormArray 是动态的。它一开始是空的,用户可以通过单击按钮将 FormGroup 添加到 FormArray

当valueChanges时,我需要重新验证控件。因为我不知道哪个控件发出了事件,所以我循环遍历整个 FormArray 并验证所有 FormGroup/FormControl,即使只有一个控件发生变化 - 这是每次数组变化。我怎样才能避免这样做?

        this.myFormArray
        .valueChanges
        .subscribe(data => this.onValueChanged(data));

    onValueChanged(data?: any): void {

    // the data I receive is an entire form array.
    // how can I tell which particular item emitted the event, 
    // so I don’t need to loop through entire array and run validation for all items.

    for (let control in this.myFormArray.controls) {
        // run validation on each control.
    }
}

您可以尝试这样的操作,但我不确定它是否有效

merge(...this.myFormArray.controls.map(control => control.valueChanges))
  .subscribe(this will be one of your form controls' value);

不在我的 PC 上进行测试,但也许使用函数的调用者 属性 可能会引导您朝着您想要的方向前进。虽然这个属性不推荐:

This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/caller

粗略示例:

this.myFormArray
    .valueChanges
    .subscribe(onValueChanged);

onValueChanged(data?: any): void {
     var whoYouGonnaCall = onValueChanged.caller.caller...caller;
     ...
}

更新

另一种选择是存储最后一个表单值并使用类似 lodash 的东西来比较属性。

var lastFormValue = this.myFormArray.value; // maybe in an init function
this.myFormArray
    .valueChanges
    .subscribe(onValueChanged);

onValueChanged(data?: any): void {
     var diff = _.omitBy(data, function(v, k) {
         return lastFormValue[k] === v;
     });
     this.lastFormValue = this.myFormArray.value; // Update for future requests
     // diff will contain the properties if the form that changed.
     ...
}

我通过在 formGroup 中添加一个 formControl(名为 groupIndex)来跟踪索引并在 formGroup 级别订阅 valueChanges 来解决这个问题formArray 级。在 valueChanges 事件中,我可以访问存储当前索引的 formControl

this.myMainFormGroup = this.myFormBuilder.group({
  // other formControls
  myFormArray: this.myFormBuilder.array([this.addArrayItem()])
});

// this method will be called every time the add button is clicked
addArrayItem(): FormGroup {
  const itemToAdd = this.myFormBuilder.group({
    // dont forget add input control for groupIndex in html for this. It will give error otherwise.
    // i made the input control hidden and readonly
    groupIndex:"", 
    firstName:["", [validator1, validator2]]
    //other formControls

  });

  const myFormArray = <FormArray>this.myMainForm.get("myFormArray");

  //set groupIndex
  itemToAdd.get("groupIndex").patchValue(myFormArray.length -1);

  //subscribe to valueChanges
  itemToAdd.valueChanges
    .debounceTime(200)
    .subscribe(data => this.onValueChanged(data));

  myFormArray.push(itemToAdd);
}

onValueChanged(data?: any): void {
  const groupIndex = data["groupIndex"];

  const myChangedGroup = <FormArray>this.myMainForm.get("myFormArray").controls[groupIndex];

  // now I have hold of the group that changed without having to iterate through the entire array. 
  // run through the custom validator 
  this.generalValidator(myChangedGroup);
}

要建立在 epsilon 的答案之上,它会收集一组 valueChanges 可观察对象并合并它们 - 您还可以通过管道传递值更改,这会通过 map 将必要的上下文添加到更改流中。

merge(...this.formArray.controls.map((control: AbstractControl, index: number) =>
        control.valueChanges.pipe(map(value => ({ rowIndex: index, value })))))
      .subscribe(changes => {
        console.log(changes);
      });

输出:

{ 
  rowIndex: 0
  value: {
    <current value of the changed object>
  }
}

请注意,第一次调用映射(在控件上)是在一个数组上。第二个映射(在管道中)是一个 RxJs 映射。我真的很喜欢这个网站来帮助这些操作员弄清楚并想象这些事件流是什么样子的:https://rxmarbles.com/#map (EDIT: I now think this post is far better: https://indepth.dev/learn-to-combine-rxjs-sequences-with-super-intuitive-interactive-diagrams/)

编辑: 因为我正在观察用户可以通过 add/delete 按钮修改的 FormArray,所以我添加了一个 changesUnsubscribe 主题并在 takeUntil 中引用它。这允许我在列表更改时丢弃旧的手表并设置新的手表。所以现在我在列表中添加或删除项目时调用 watchForChanges()。

changesUnsubscribe = new Subject();
...
watchForChanges() {
  // cleanup any prior subscriptions before re-establishing new ones
  this.changesUnsubscribe.next();

  merge(...this.formArray.controls.map((control: AbstractControl, index: number) =>
            control.valueChanges.pipe(
                takeUntil(this.changesUnsubscribe),
                map(value => ({ rowIndex: index, control: control, data: value })))
    )).subscribe(changes => {
            this.onValueChanged(changes);
    });
}

将 FormArray 的控件绑定到模型中的数组时,您可以将索引或任何其他 id 从模型传递到事件处理程序:

 <div *ngFor="let m of modelArr; let ix = index" formArrayName="ControlArr">
    <input type="checkbox" [formControlName]="ix" (change)="my_handler($event, ix, m.id)">
 </div>

在组件打字稿中:

 my_handler(ev: any, ix: number, id: any): void {
    console.log(ix, id);
 }