0
votes

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?

1
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.JB Nizet
division[i] is also incorrect: division is a FormGroup.JB Nizet
@JBNizet do you have a working version you could put in an answer?DJC

1 Answers

0
votes

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[].