I have an array of FormGroup
s which all holds one FormControl
called checked
which is represented as a checkbox input in the template.
This array formGroups$
is computed from another Observable
called items$
.
// component.ts
constructor(private fb: FormBuilder) {}
items$ = of([{ whatever: 'not used' }, { something: 'doesnt matter' }]);
// doesn't work!
formGroups$: Observable<FormGroup<{ checked: FormControl<boolean> }>[]> =
this.items$.pipe(
map((items) => {
const array: FormGroup[] = [];
for (const item of items) {
const formGroup = this.fb.group({});
formGroup.addControl('checked', new FormControl(false));
array.push(formGroup);
}
return array;
})
);
allChecked$: Observable<boolean> = this.formGroups$.pipe(
switchMap((formGroups) => {
const valueChangesArray: Observable<boolean>[] = [];
formGroups.forEach((formGroup) => {
valueChangesArray.push(
formGroup
.get('checked')
.valueChanges.pipe(startWith(formGroup.get('checked').value))
);
});
return combineLatest(valueChangesArray);
}),
map((checkedValues) => {
console.log(checkedValues);
for (const isChecked of checkedValues) {
if (!isChecked) {
return false;
}
}
return true;
})
);
<!-- component.html -->
<ng-container *ngFor="let formGroup of formGroups$ | async; index as i">
<label>
<input type="checkbox" [formControl]="formGroup.controls.checked" />
{{ i }}
</label>
</ng-container>
<p>allChecked: {{ allChecked$ | async }}</p>
Example see also in Stackblitz:
If I change formGroup$
to a simpler static solution, the value allChecked$
is computed correctly every time:
// works!
formGroups$: Observable<FormGroup<{ checked: FormControl<boolean> }>[]> = of([
new FormGroup({
checked: new FormControl(false),
}),
new FormGroup({
checked: new FormControl(true),
}),
]);
You can easily reproduce it in this StackBlitz: https://stackblitz.com/edit/angular-ivy-xfpywy?file=src/app/app.component.ts
How do I compute this boolean allChecked$
with an array of dynamically created FormGroup
s?
CodePudding user response:
It looks like the issue with your code is that you are using the of
operator to create an observable from your items$
array, but of
creates a cold observable that only emits a single value (the array) and then completes. As a result, your formGroups$
observable only emits a single value and never updates.
To fix this issue, you could use the from
operator instead of of
. The from
operator creates an observable from an array (or any other iterable object), and it will emit the values in the array one at a time. This will allow your formGroups$
observable to update when the values in items$
change.
Here is an example of how you could update your code to use the from
operator:
// component.ts
constructor(private fb: FormBuilder) {}
items$ = from([[{ whatever: 'not used' }, { something: 'doesnt matter' }]]);
formGroups$: Observable<FormGroup<{ checked: FormControl<boolean> }>[]> =
this.items$.pipe(
map((items) => {
const array: FormGroup[] = [];
for (const item of items) {
const formGroup = this.fb.group({});
formGroup.addControl('checked', new FormControl(false));
array.push(formGroup);
}
return array;
})
);
allChecked$: Observable<boolean> = this.formGroups$.pipe(
switchMap((formGroups) => {
const valueChangesArray: Observable<boolean>[] = [];
formGroups.forEach((formGroup) => {
valueChangesArray.push(
formGroup
.get('checked')
.valueChanges.pipe(startWith(formGroup.get('checked').value))
);
});
return combineLatest(valueChangesArray);
}),
map((checkedValues) => {
console.log(checkedValues);
for (const isChecked of checkedValues) {
if (!isChecked) {
return false;
}
}
return true;
})
);
CodePudding user response:
You have two different arrays of formGroups!! I Imagine you can use ShareReply to not create a new formGroup each time you subscribe
But You can also use "tap" operator to create the allChecked$ Observable
allChecked$: Observable<boolean>;
formGroups$: Observable<FormGroup<{ checked: FormControl<boolean> }>[]> =
this.items$.pipe(
map((items) => {
const array: FormGroup[] = [];
for (const item of items) {
const formGroup = this.fb.group({});
formGroup.addControl('checked', new FormControl(false));
array.push(formGroup);
}
return array;
}),
tap((formGroups: any[]) => { //here we create the Observable
//see that "formGroups" are your array of FormGroups
const valueChangesArray: Observable<boolean>[] = [];
formGroups.forEach((formGroup) => {
valueChangesArray.push(
formGroup
.get('checked')
.valueChanges.pipe(startWith(formGroup.get('checked').value))
);
});
this.allChecked$ = combineLatest(valueChangesArray).pipe(
map((checkedValues) => {
for (const isChecked of checkedValues) {
if (!isChecked) {
return false;
}
}
return true;
})
);
})
);
Your forked stackblitz