How to identify which item in FormArray emitted valueChanges event?

I resolved this issue by adding a formControl (named groupIndex) in the formGroup to track the index and subscribing to the valueChanges at the formGroup level instead of formArray level. On valueChanges event, I could then access the formControl that stored the current index.

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);
}

To build on epsilon's answer, which collects an array of valueChanges observables and merges them - you can also pipe the value changes thru a pipe, which adds necessary context to the changes stream via map.

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

Output:

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

Note that the first call to map (on controls) is on an array. The second map (in the pipe) is an RxJs map. I really like this website to help get these operators straight and imagine what these streams of events look like: 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/)

EDIT: Because I was watching a FormArray that could be modified by the user via the add/delete buttons, I added a changesUnsubscribe subject and reference that in the takeUntil. This allows me to discard the old set of watches and setup new ones when the list changes. So now I call watchForChanges() when items are added or removed from the list.

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);
    });
}

You can try something like this, but I am not sure that it will work

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