Search code examples
angulartypescriptform-control

Angular formArray Cannot find control with path


Good afternoon, I will be glad for help, because for many days I have been looking everywhere for an answer to this question, but I found only special cases. Thank you in advance.

I'm trying to make a dynamic form. I know that this question has been asked more than once, but I have not found a solution, since my object formBuilder.

The array consists of two fields (URL: string, social: string) because it can't find the control with path:

ERROR Error: Cannot find control with path: 'socialNetworks -> 0 -> url'
ERROR Error: Cannot find control with path: 'socialNetworks -> 0 -> social'
ERROR Error: Cannot find control with path: 'socialNetworks -> 1 -> social'
ERROR Error: Cannot find control with path: 'socialNetworks -> 1 -> url'

My solution: stackblitz

create.component.html

<form mat-dialog-content [formGroup]="form">
    <div class="social-component" *ngFor="let network of networksFieldAsFormArray.controls; let i = index; trackBy: trackByFn;" formArrayName='socialNetworks'>
        <mat-form-field appearance="outline" [formGroupName]="i">            
            <input matInput formControlName="url" placeholder="Social url">
            <mat-icon class="deleteSocial" matSuffix (click)="remove(i)">close</mat-icon>

            <mat-select formControlName="social" placeholder="Social name">
                <mat-option value="{{name}}" *ngFor="let name of socialName; let i = index; trackBy: trackByFn;">
                    {{name}}
                </mat-option>
            </mat-select>

            <mat-error *ngIf="errorUrlSocialNetworks(i)">
                &#171;Social url&#187; is <strong>required</strong>
            </mat-error>
       </mat-form-field>
    </div>
</form>

create.component.ts

export interface InputSocial {
  social: string,
  url: string
}

@Component({
  selector: 'app-create',
  templateUrl: './create.component.html',
  styleUrls: ['./create.component.scss']
})
export class CreateComponent implements OnInit {
  public form: FormGroup = new FormGroup('', []);
  socialName: string[] = ['facebook', 'phone', 'email', 'feedback'];
  socialNetworks: InputSocial[] = [];

  constructor(
    private formBuilder: FormBuilder,
    @Inject(MAT_DIALOG_DATA) public data: any,
  ) { }

  ngOnInit(): void {
    this.form = this.formBuilder.group({
      firstName: new FormControl(this.data.row.firstName || '', Validators.required),
      middleName: new FormControl(this.data.row.middleName || '', Validators.required),
      lastName: new FormControl(this.data.row.lastName || '', Validators.required),
      socialNetworks: this.formBuilder.array(this.data.row.communication || [])
    });
  }

  get networksFieldAsFormArray(): any {
    return this.form.get('socialNetworks') as FormArray;
  }

  networks(): any {
    return this.formBuilder.group({
      url: this.formBuilder.control('', Validators.required),
      social: this.formBuilder.control('feedback', Validators.required)
    });
  }
}

Solution

  • To use formBuilder.array() you need feed with an array of FormGroups (or an array of FormControls). You're passing an array of objects, so you are not create the formArray

    To generate the formArray, you generally make it "mapping" your data

    ...
    socialNetworks: this.formBuilder.array(data && data.communication?
                    data.communication.map(x=>this.networks(x)):[])
    

    But better improve your "this.networks" passing the data -this is the reason I pass the "x"-

      networks(data:any=null): any {
        data={url:'',social:'feedback',...data}
        return this.formBuilder.group({
          url: this.formBuilder.control(data.url, Validators.required),
          social: this.formBuilder.control(data.social, Validators.required),
        });
      }
    

    NOTE: you should avoid mix the constructor of FormControls and the methods of formbuilder, so, better

       this.form = this.formBuilder.group({
          firstName: [data.firstName || '', Validators.required],
          middleName: [data.middleName || '', Validators.required],
          lastName: [data.lastName || '', Validators.required],
          socialNetworks: this.formBuilder.array(data && data.communication?data.communication.map(x=>
            this.networks(x)
          ):[]),
        });