Good evening, I have a problem I am using a reactive form, with FormArray, I have a button to add 2 options as many times as necessary, the problem is that the options are matselects and in one of these selects I use an event (change), the problem is that adding the options for the first time the onchange works normal, but when adding the options more than 2 times the values that were previously selected in the matselects change because the onchange of the created select is re-executed. I would like to know if there is a way to use that event independently in each iteration of the formArray
Component.ts
alicuotaForm: FormGroup;
value = this.fb.group({
manzana: ['', Validators.required],
villa: ['', Validators.required],
});
nregistros: number;
ngOnInit() {
this.alicuotaForm = this.fb.group({
times: this.fb.array([]),
});
}
//event onchange
getVillas(value) {
console.log('valor: ', value.value);
this.filtro = this.villas.filter((x) => x.manzana == value.value);
}
addGroup() {
const val = this.fb.group({
manzana: ['', Validators.required],
villa: ['', Validators.required],
});
const alicuotaForm = this.alicuotaForm.get('times') as FormArray;
alicuotaForm.push(val);
this.nregistros = alicuotaForm.length;
}
removeGroup(index) {
const alicuotaForm = this.alicuotaForm.get('times') as FormArray;
alicuotaForm.removeAt(index);
}
trackByFn(index: any, item: any) {
return index;
}
component.html
<div class="container">
<div class="row">
<div class="col-col-6">
<label class="transparente"></label>
<button (click)="addGroup()" type="button" class="btn btn-success">
Agregar Campos
</button>
</div>
<br />
<form [formGroup]="alicuotaForm">
<ng-container
formArrayName="times"
*ngFor="
let time of alicuotaForm.controls.times?.value;
let i = index;
trackBy: trackByFn
"
>
<ng-container [formGroupName]="i" style="margin-bottom: 10px;">
Iteracion {{ i 1 }}
<div class="col-col-6">
<label>Manzana</label>
<select
formControlName="manzana"
class="form-control custom-select"
(change)="getVillas($event.target)"
>
>
<option *ngFor="let ur of manzanas">
{{ ur.manzana }}
</option>
</select>
</div>
<br />
<div class="col-sm">
<label>Villa</label>
<select formControlName="villa" class="form-control custom-select">
<option *ngFor="let ur of filtro" value="{{ ur.villa }}">
{{ ur.villa }}
</option>
</select>
</div>
<br />
------------------------------------------------------------------------<br /> </ng-container
></ng-container>
</form>
</div>
</div>
I don't know what I could do or what would you recommend me to solve my problem, thank you very much
code in stackblitz Stackblitz
CodePudding user response:
You have to create the separate value for filtro you can get help from the following code
HTML
<hello name="{{ name }}"></hello>
<p>Start editing to see some magic happen :)</p>
<div >
<div >
<div >
<label ></label>
<button (click)="addGroup()" type="button" >
Agregar Campos filtro
</button>
</div>
<br />
<form [formGroup]="alicuotaForm">
<ng-container
formArrayName="times"
*ngFor="
let time of alicuotaForm.controls.times?.value;
let i = index;
trackBy: trackByFn
"
>
<ng-container [formGroupName]="i" style="margin-bottom: 10px;">
Iteracion {{ i 1 }}
<div >
<label>Manzana</label>
<select
formControlName="manzana"
(change)="getVillas($event.target, i)"
>
>
<option *ngFor="let ur of manzanas">
{{ ur.manzana }}
</option>
</select>
</div>
<br />
<div >
<label>Villa</label>
<select formControlName="villa" >
<option *ngFor="let ur of filtro[i]" value="{{ ur.villa }}">
{{ ur.villa }}
</option>
</select>
</div>
<br />
------------------------------------------------------------------------<br /> </ng-container
></ng-container>
</form>
Typescript
import { Component, VERSION } from '@angular/core';
import {
FormControl,
FormGroup,
FormBuilder,
FormArray,
Validators,
FormGroupDirective,
} from '@angular/forms';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
name = 'Angular ' VERSION.major;
villas = [
{ manzana: '1', villa: '10' },
{ manzana: '1', villa: '20' },
{ manzana: '1', villa: '30' },
{ manzana: '2', villa: '40' },
{ manzana: '2', villa: '50' },
{ manzana: '2', villa: '60' },
{ manzana: '3', villa: '70' },
{ manzana: '3', villa: '80' },
{ manzana: '3', villa: '90' },
];
filtro: any[]=[];
manzanas = [{ manzana: '1' }, { manzana: '2' }, { manzana: '3' }];
alicuotaForm: FormGroup;
value = this.fb.group({
manzana: ['', Validators.required],
villa: ['', Validators.required],
});
nregistros: number;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.alicuotaForm = this.fb.group({
times: this.fb.array([]),
});
}
getVillas(value,index) {
console.log('valor: ', value.value);
this.filtro[index] = this.filtro[index].filter(x => x.manzana === value.value);
}
addGroup() {
const val = this.fb.group({
manzana: ['', Validators.required],
villa: ['', Validators.required],
});
const alicuotaForm = this.alicuotaForm.get('times') as FormArray;
alicuotaForm.push(val);
this.nregistros = alicuotaForm.length;
this.filtro.push(null);
this.filtro[this.nregistros-1]=[...this.villas];
}
removeGroup(index) {
const alicuotaForm = this.alicuotaForm.get('times') as FormArray;
alicuotaForm.removeAt(index);
}
trackByFn(index: any, item: any) {
return index;
}
}
CodePudding user response:
From what I can tell, your main problem is that you have a single variable, filtro
, that you're trying to use for all of the elements in the FormArray. You can either:
- (My recommended) Create a custom
Pure
pipe to return a list of filtered villas. Pure pipes will only execute when the value changes, instead of every paint cycle.
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'arrayFilter' })
export class ArrayFilterPipe implements PipeTransform {
transform(arr: any[], property: string, value: any): any {
return arr.filter((x) => x[property] === value);
}
}
You then use it in your template like so (Don't forget to declare it in your module):
<select formControlName="villa" class="form-control custom-select">
<option
*ngFor="let ur of (villas | arrayFilter: 'manzana': time.manzana)"
value="{{ ur.villa }}"
>
{{ ur.villa }}
</option>
</select>
Typescript:
addGroup() {
// either provide a '' option to this.manzanas, or set manzana: '1' to avoid expression after checked errors
const val = this.fb.group({
manzana: ['1', Validators.required],
villa: ['', Validators.required],
});
const alicuotaForm = this.alicuotaForm.get('times') as FormArray;
alicuotaForm.push(val);
this.nregistros = alicuotaForm.length;
}
For more information, see Pipes.
- Create another form control on your group called
filtro
, and you set its value in thegetVillas
function. You would then reference this form control's value when you do the*ngFor
for the form controlvilla
- Create a function in your component that filters villas based on the array item's manzana (don't recommend, since this would recalculate every paint cycle).