angularangular-reactive-formsform-control

Angular Cannot find control with unspecified name attribute


I know this question coming up multiple times every year but I can't get this running. I need to have dynamically created formArray depending on the elements given by an array. Tried multiple attempts and I'm stuck to try the on of medium: https://medium.com/aubergine-solutions/add-push-and-remove-form-fields-dynamically-to-formarray-with-reactive-forms-in-angular-acf61b4a2afe

My html:

<mat-accordion class="example-headers-align">
    <form [formGroup]="formGroup">
        <mat-expansion-panel [expanded]="step === item.step" (opened)="setStep(item.step)" hideToggle
            *ngFor="let item of csvColumns; let i = index" formArrayName="selectionArray">
            <mat-expansion-panel-header>
                <mat-panel-title>
                    {{item.title}}
                </mat-panel-title>
                <mat-panel-description>
                    {{i}} {{item.description}}
                </mat-panel-description>
                <fa-icon [icon]="['fas', 'list-ul']" size="lg" style="color: grey"></fa-icon>
            </mat-expansion-panel-header>

            <mat-form-field>
                <mat-label>{{'CSV.DBATTR' | translate}}</mat-label>
                <input matInput placeholder="{{'CSV.CHOOSE' | translate}}" [matAutocomplete]="auto"
                    [formControl]="selectionArray[i]">
                <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
                    <mat-option>{{'CSV.NONE' | translate}}</mat-option>
                    <mat-optgroup *ngFor="let group of filteredOptions | async" [label]="group.name">
                        <!-- <fa-icon [icon]="['fas', 'table']" size="lg" class="tree-icons"></fa-icon> -->
                        <mat-option *ngFor="let children of group.children" [value]="children.name">
                            <!-- <fa-icon [icon]="['fas', 'tag']" size="md" class="tree-icons"></fa-icon> -->
                            {{children.name}}
                        </mat-option>
                    </mat-optgroup>
                </mat-autocomplete>
            </mat-form-field>

            <mat-action-row>
                <button *ngIf="item.step !== 0" mat-button color="warn"
                    (click)="prevStep()">{{'CSV.PREV' | translate}}</button>
                <button *ngIf="item.step < csvColumns.length-1" mat-button color="primary"
                    (click)="nextStep()">{{'CSV.NEXT' | translate}}</button>
                <button *ngIf="item.step === csvColumns.length-1" mat-button color="primary"
                    (click)="nextStep()">{{'CSV.END' | translate}}</button>
            </mat-action-row>
        </mat-expansion-panel>
    </form>
</mat-accordion>

To explain that code: *ngFor="let item of csvColumns; let i = index" it creates dynamically as much items as my array contains. E.g. 5 elements in there create 5 extension panels. I need to be able to access the value that the user chooses for each of this elements in this selection dropdown. So my idea is to create in the step where the extension panels are made formControls for each panel.

That's why I want to use a formArray. If you think that's a bad attempt, tell me a better one.

My ts:

export class CSVMappingComponent implements OnInit {

  @Input() headerColumns: Array<string>;

  csvColumns: Array<ExtensionPanelModel>;

  filteredOptions: Observable<DatabaseGroup[]>;
  databaseGroups: DatabaseGroup[];

  formGroup: FormGroup;


  constructor(private builder: FormBuilder) {
    this.formGroup = this.builder.group({
      selectionArray: this.builder.array([])
    });
  }

  // i don't need this but i'm trying everything
  createBox(): FormGroup {
    return this.builder.group({
      name: ''
    });
  }

  get selectionArray() {
    return this.formGroup.get('selectionArray') as FormArray;
  }

  addItems() {
    this.csvColumns = [];
    for (var i = 0; i < this.headerColumns.length; i++) {
      let _step = i.toString();

      this.csvColumns.push({ title: this.translateService.instant('CSV.COLUMN') + ": " + this.headerColumns[i], description: this.translateService.instant('CSV.DESCRIPTION'), step: i });
      this.selectionArray.push(this.createBox());
      // here I really don't know what to do bc I don't need any validator just access the value
      // this.selectionArray.push(this.builder.control(false));
    }
  }

And this filteredOptions autocomplete thing is from before with only one formControl but of course that can not work.

The errors are ERROR Error: Cannot find control with unspecified name attribute or something like ERROR Error: Cannot find control with path: 'selectionArray -> 'when I use formControlName="{{selectionArray[i]}}

A screenshot to let you know how it looks or should look in the end: enter image description here


Solution

  • You can't access formarray controls as selectionArray[i], try like

    <div *ngFor="let control of selectionArray.controls; let i = index">
    ...
       <input matInput placeholder="{{'CSV.CHOOSE' | translate}}" [matAutocomplete]="auto" formControlName="{{i}}">
    ...
    
    </div>