0

Having fully digested the Angular Reactive forms docs, I'm still struggling to implement the following functionality:

Using a list of Divisions from my NgRx store, output a mat-checkbox for each with the division name and it's checked status (using division.current property - so that the currently active divisions are checked by default), within an Angular reactive form.

Here's where I'm at:

new-season-component.ts

export class NewSeasonComponent implements OnInit {
  chooseDivisions: FormGroup;
  divisions: FormArray;
  allDivisions: Division[];
  constructor(
      private fb: FormBuilder,
      private store: Store<AppState>
  ) {
    }
  ngOnInit() {
    this.store.dispatch(new AllDivisionsRequested());
    this.store
        .pipe(
            select(selectAllDivisions)
        ).subscribe(divisions => this.allDivisions = divisions);

    this.chooseDivisions = this.fb.group({
      divisions: this.fb.array([])
    });

    this.addDivisions();

  }
  createDivisionFormControl(division: Division): FormGroup {
    return this.fb.group({
      id: division.id,
      name: division.name,
      current: division.current
    });
  }
  addDivisions(): void {
    this.divisions = this.chooseDivisions.get('divisions') as FormArray;
    this.allDivisions.map(
        division => {
          this.createDivisionFormControl(division);
        }
    );
  }

In summary - grabbing divisions from the store, creating a formGroup containing an empty formArray. Then running a method to map over the divisions and call another method to create them as formGroups.

** new-season-component.html**

<form [formGroup]="chooseDivisions">
  <ng-template matStepLabel>Choose which teams are part of this season</ng-template>
  <div formArrayName="divisions">
    <div *ngFor="let division of chooseDivisions.get('divisions').controls; let i = index;">
      <div [formGroupName]="i">
        <mat-checkbox formControlName="division">{{ division[i].name }}</mat-checkbox>
      </div>
    </div>
  </div>
  <div>
    <button mat-button matStepperNext>Next</button>
  </div>
</form>

I've used various online material to get to this point - included this article but none seem to quite be what I'm looking for - ie adding a list of divisions as checkboxes on load, rather than the functionality to click a button to add a new blank form control.

I'm obviously doing something fundamentally wrong. Should I even be using a reactive form here?

3
  • There is no form control named division inside the forms groups in the form array. The form controls are named id, name and current. So formControlName="division" is not correct. Commented Aug 6, 2019 at 16:29
  • division[i] is also incorrect: division is a FormGroup. Commented Aug 6, 2019 at 16:31
  • @JBNizet do you have a working version you could put in an answer? Commented Aug 7, 2019 at 16:20

1 Answer 1

0

Form array's must always be a child of Form Group, otherwise you'll throw errors. So you would have to initiate a form group in ngOnInit with the form array as a control.

this.divisionsFormGroup = this.fb.group({
    divisions: this.fb.array([])
});

I'd recommend pushing into the form array afterwards:

this.allDivisions.map(divisionFromStore => this.divisionsFormGroup.controls.divisions.push(createDivisionFormControl(divisionFromStore));

Then your HTML:

<form [formGroup]="divisionsFormGroup">
  <ng-template matStepLabel>Choose which teams are part of this season</ng-template>
  <div formArrayName="divisions">
    <div *ngFor="let division of divisions.controls; let i = index;"> // I can't quite recall if you can call controls off of the formArrayName, or if you have to use divisionsFormGroup.controls.divisions.controls
      <div [formControlName]="i">
        <mat-checkbox>{{ division.controls.name.value }}</mat-checkbox> // Try just division.name if that doesn't work
      </div>
    </div>
  </div>
  <div>
    <button mat-button matStepperNext>Next</button>
  </div>
</form>

Issues with typing...

  • Cast the return value of the form group creation method

return this.fb.group({etc}) as AbstractControl // I can't quite recall if it'll error out and say there's no overlap between properties. If that doesn't work, do the below...

  • Use the form array's setValue() instead.

const arrayOfAllDivisionsFormGroups = this.allDivisions.map(divisionFromStore => createDivisionFormControl(divisionFromStore));

this.divisionsFormGroup.controls.divisions.setValue(arrayOfAllDivisionsFormGroups);

There shouldn't be a type clash b/c setValue() takes any[].

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

2 Comments

Hmmm - I already have a form group with the form array inside - chhoseDivisions -> divisions. Also I get the error 'Property 'push' does not exist on type 'AbstractControl'' when I use you code to push into the array
Ok. So anywhere you see divisionsFormGroup in the template and component, replace with chooseDivisions. A workaround for the typing issue is to cast the return value of createDivisionFormControl to AbstractControl. If you don't want to do that, you can use the form array's setValue. I'll put some code above.

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.