web-dev-qa-db-fra.com

Comment identifier quel élément de FormArray a émis l'événement valueChanges?

Dans Angular, existe-t-il un moyen d'identifier quels FormGroup/FormControl dans un dynamiqueFormArray ont émis le valueChanges un événement?

Mon FormArray est dynamique. Il commence vide et les utilisateurs peuvent ajouter un FormGroup au FormArray en cliquant sur un bouton.

Lorsque valueChanges, j'ai besoin de revalider le contrôle. Étant donné que je ne sais pas quel contrôle a émis l'événement, je fais une boucle à travers l'ensemble du FormArray et je valide tous les FormGroup/FormControl même si un seul contrôle a changé - et c'est à chaque fois que quoi que ce soit dans le tableau change. Comment puis-je éviter de faire cela?

        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.
    }
}
10
anish

J'ai résolu ce problème en ajoutant un formControl (nommé groupIndex) dans le formGroup pour suivre l'index et en m'abonnant au valueChanges au niveau formGroup au lieu de formArray niveau. Sur l'événement valueChanges, je pouvais alors accéder à formControl qui stockait l'index en cours.

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

Vous pouvez essayer quelque chose comme ça, mais je ne suis pas sûr que cela fonctionnera

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

Pas sur mon PC pour tester, mais peut-être que l'utilisation de la propriété caller d'une fonction peut vous guider dans la direction que vous souhaitez. Bien que cette propriété ne soit pas recommandée:

Cette fonctionnalité n'est pas standard et n'est pas sur une piste standard. Ne l'utilisez pas sur des sites de production tournés vers le Web: cela ne fonctionnera pas pour tous les utilisateurs. Il peut également y avoir de grandes incompatibilités entre les implémentations et le comportement peut changer à l'avenir.

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

exemple approximatif:

this.myFormArray
    .valueChanges
    .subscribe(onValueChanged);

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

Mettre à jour

Une autre option consiste à stocker la dernière valeur du formulaire et à utiliser quelque chose comme lodash pour comparer les propriétés.

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.
     ...
}
0
Carlos P.

Pour s'appuyer sur la réponse d'Epsilon, qui collecte un tableau d'observables valueChanges et les fusionne - vous pouvez également diriger les changements de valeur via un tuyau, ce qui ajoute le contexte nécessaire au flux de changements via la carte.

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

Production:

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

Notez que le premier appel à mapper (sur les contrôles) est sur un tableau. La deuxième carte (dans le tube) est une carte RxJs. J'aime vraiment ce site Web pour aider à clarifier ces opérateurs et imaginer à quoi ressemblent ces flux d'événements: https://rxmarbles.com/#map

[~ # ~] modifier [~ # ~] : parce que je regardais un FormArray qui pouvait être modifié par l'utilisateur via les boutons d'ajout/suppression, J'ai ajouté un sujet changesUnsubscribe et une référence dans le takeUntil. Cela me permet de jeter l'ancien ensemble de montres et d'en configurer de nouvelles lorsque la liste change. Alors maintenant, j'appelle watchForChanges () lorsque des éléments sont ajoutés ou supprimés de la liste.

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

Si quelqu'un essaie de convertir le type d'entrée = "nombre" en un entier avec un tableau de contrôles en utilisant angular formes dynamiques.

Cet extrait renvoie le contrôle réel qui a été modifié.

const formArray = this.parentFormGroup.controls['obligationList'] as FormArray
    formArray.controls.forEach(control => {
        control.valueChanges
            .debounceTime(800)
            .distinctUntilChanged()
            .takeUntil(this.ngUnsubscribe)
            .subscribe(() => control.patchValue({amountPayable: parseInt(control.value['amountPayable'], 10)}, {emitEvent : false}))
    })
0
Levarne Sobotker