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