0

I have an array of FormGroups 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: https://stackblitz.com/edit/angular-ivy-xfpywy?file=src%2Fapp%2Fapp.component.ts

Now when I try to compute if all those checkboxes are checked with allChecked$, which combines all Observables from each FormGroups formControl.valueChanges, the map in there only gets triggered once and not as expected every time the value of a Checkbox changes:

enter image description here

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%2Fapp%2Fapp.component.ts

How do I compute this boolean allChecked$ with an array of dynamically created FormGroups?

1 Answer 1

1

Update 03, december

Yes, as @akoted comment, really you have two different arrays of formGroups!!

We can check if we declare a variable

forms:any[]=[] 

and write

formGroups$ =
    this.items$.pipe(
      ...
    ,
    tap(res=>{
        this.forms[this.forms.length]=res
    })

Then use {{forms[0]==forms[1]}} in .html.

One solution can be use SharedReply

formGroups$ =
    this.items$.pipe(
      ...
    ,
    sharedReply(1)
)

Another one is 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

Sign up to request clarification or add additional context in comments.

4 Comments

Where exactly do I have two different arrays of FormGroups? I only have formGroups$ defined once... Also, how would I use shareReplay in this example?
@jones1008, my Bad! your'e correct, Now I feel that is the same formGrop[]. I'll try to understand why not work and revised my answer :(
@Eliseo you were right. He has two different FormGroup[]. The formGroups$ async subscription has a different instance than the allChecked$ async sub. // btw in the random$ test you've made in your stackblitz, you are getting the same number cause the Math.random doesnt get executed for each subscription. If you use defer(() => of(Math.random()))` instead you'll see that you get a different number for each sub.
@akotech, Yes, they are different array, we can check it, e.g. we create an array forms, and adding a tapthis.forms[this.forms.length]=res, then we check if this.forms[0]==this.forms[1]. We can also use formGroups[0].pacthValue({checked:true})` and we can see that they are dirrecnets, Thanks by your comment

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.