I'm creating an "Invite members" form in which a user can add any number of "members" to the form to invite at once. I have this piece figured out and working.
I also have a dynamically created list of checkboxes that correspond to a team that a user can be associated with, so I need to create those controls on the fly, based on a dynamic list.
The problem I'm having is that my form seems to be getting a bit complex from all the nesting, and due to the dynamic nature of these teams, I'm running into some issues. The main issue I'm getting is an error:
error_handler.js:45 EXCEPTION: Error in ./InviteMembersComponent class InviteMembersComponent - inline template:60:12 caused by: Cannot find control with path: 'members -> teams'
For brevity, here is the code for just the relevant pieces of functionality:
HTML:
<form [formGroup]="inviteMembersForm" novalidate (ngSubmit)="onInviteMembers(inviteMembersForm.value, inviteMembersForm.valid)" class="ui form">
      <ul>
        <li formArrayName="members">
          <div *ngFor="let member of inviteMembersForm.controls.members.controls; let i=index">
            <ul class="members-info"
              [formGroupName]="i">
              <li class="email">
                <h4>Email Address</h4>
                <sm-input [control]="inviteMembersForm.controls.members.controls[i].controls.email" class="left fluid" placeholder="[email protected]"></sm-input>
                <small [hidden]="inviteMembersForm.controls.members.controls[i].controls.email.valid || (inviteMembersForm.controls.members.controls[i].controls.email.pristine && !submitted)">
                    A valid email is required
                </small>
              </li>
              <li class="first-name">
                <h4>First Name</h4>
                <sm-input
                  [control]="inviteMembersForm.controls.members.controls[i].controls.firstName"
                  class="left fluid"
                  placeholder="Optional">
                </sm-input>
              </li>
              <li class="last-name">
                <h4>Last Name</h4>
                <sm-input
                  [control]="inviteMembersForm.controls.members.controls[i].controls.lastName"
                  class="left fluid"
                  placeholder="Optional">
                </sm-input>
              </li>
              <li class="teams-button">
                <button (click)="togglePeopleTeamsVisible(i)">
                  <span>Teams</span>
                  <i class="fa fa-chevron-down" *ngIf="!memberTeamsVisibility[i].isVisible"></i>
                  <i class="fa fa-chevron-up" *ngIf="memberTeamsVisibility[i].isVisible"></i>
                </button>
              </li>
              <li class="remove-button" *ngIf="(i + 1) > 1">
                <button (click)="removeMember(i)"><img src="/assets/images/icons/close.svg" width="20" /></button>
              </li>
            </ul>
            <div class="teams-block"
            *ngIf="memberTeamsVisibility[i].isVisible"
            formArrayName="teams">
              <ul class="team-list"
                *ngFor="let team of inviteMembersForm.controls.members.controls.teams.controls; let j=index">
                <li [formGroupName]="j">
                </li>
              </ul>
            </div>
          </div>
        </li>
        <li>
          <button class="add-invite" (click)="addMember()">
            <img src="/assets/images/icons/invite-add-blue.svg" width="16" />
            <span>Add another invitation</span>
          </button>
        </li>
        <li><input type="submit" [disabled]="!inviteMembersForm.valid" class="btn" value="Invite {{inviteMembersForm.controls.members.controls.length}} Person" /></li>
      </ul>
    </form>
Typescript:
public inviteMembersForm: FormGroup;
  public memberTeamsVisibility: any = [];
  public teams: any = [
    {
      id: 1,
      title: "Engineering",
      count: 3,
    },
    {
      id:2,
      title: "Sales",
      count: 1
    },
    {
      id:3,
      title: "Marketing",
      count: 2
    }
  ];
  constructor(
    private _fb: FormBuilder,
    private _validators: ValidatorsService
  ) {
    this.createInviteMembersForm();
  }
  ngOnInit() {
  }
  /**
   *  Creation of form for "Invite member"
   */
  createInviteMembersForm() {
    this.inviteMembersForm = this._fb.group({
      members: this._fb.array([this.initMember()])
    });
    // Add a team visibility toggle for the newly added member
    this.addMemberTeamVisibilityToggle();
     // Add the teams
    this.addTeams();
    console.log(this.inviteMembersForm.controls['members']['controls'][0]['controls']['teams']);
  }
  /**
   *  Create a form group for adding an "invite member" form
   */
  initMember() {
    return this._fb.group({
        email: ['', [<any>Validators.required, this._validators.isValidEmail]],
        firstName: ['', []],
        lastName: ['', []],
        teams: this._fb.array([])
    });
  }
  // Add teams to the form
  addTeams() {
    const teamsControl = <FormArray>this.inviteMembersForm.controls['members']['controls'][0]['controls']['teams'];
    this.teams.forEach(team => {
      teamsControl.push(this.addTeam(team.title));
    })
  }
  /**
   *  Adds a team form group to the user
   */
  addTeam(title) {
    return this._fb.group({
        team: [title, []]
    });
  }
  /**
   *  Add an entry for teams visibility anytime a new member is added
   */
  addMemberTeamVisibilityToggle() {
    let membersLength = this.inviteMembersForm.controls['members']['_value'].length;
    for(let i = 0; i < membersLength; i++) {
      this.memberTeamsVisibility.push({ isVisible: false});
    }
  }
  /**
   *  Add a member to the form
   */
  addMember() {
    // add address to the list
    const control = <FormArray>this.inviteMembersForm.controls['members'];
    control.push(this.initMember());
    this.addMemberTeamVisibilityToggle();
  }
  removeMember(i: number) {
    const control = <FormArray>this.inviteMembersForm.controls['members'];
    control.removeAt(i);
    this.memberTeamsVisibility.removeAt(i);
  }
Could someone shed some light onto what I'm missing? I feel like I've almost got it.
                        
The accessor for
teamsin the template is, indeed, a little bit incorrect.You'll need to make sure you set the
formGroupNameyou're accessing just like in yourulup above and be sure to hit the right control path:Taking things a little further and getting the bindings done as well will require a couple additional tricks up our sleeve to make things easier and considerably more automatic. The teams groups instantiations will change to:
So that we can access our object keys in the template (with a keys pipe) modified by taking an argument for when we know we have a single key and want a straight transform
return args ? (args.indexOf('single') > -1 ? key : keys) : keys;which means we can write our template now as:Here's a Plunker that you can play around in. I also modified the visibility array to make things a touch simpler.