0

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

2 Answers 2

1

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>
Sign up to request clarification or add additional context in comments.

4 Comments

I get the: ERROR TypeError: control.registerOnChange is not a function. I've put the div after the line with <mat-expansion-panel>
Replace [formControl] with formControlName="{{i}}" <div *ngFor="let control of selectionArray.controls; let i = index;">
the same error... ok wait I changed in my ts to this.selectionArray.push(this.builder.control(false)); and this gives no error but the whole structure is completly buggy. Instead of displaying my whole csvColumns array there is just the first one and the dropdown says false. If I click on a button next or prev it's for all the panels. So the number of panels is correct but they're all the same one.
I already get the problem there. I can't have a ngFor in the other ngFor because ngFor of mat-extension-panel is creating every single panels. If you have it inside this panel the things happen that I see. Is it possible to have two ngFors or something?
0

To let you guys know which changes worked:

<mat-accordion class="example-headers-align">
    <form [formGroup]="formGroup">
        <div formArrayName="selectionArray">
            <mat-expansion-panel [expanded]="step === item.step" (opened)="setStep(item.step)" hideToggle
                *ngFor="let item of csvColumns; let control of selectionArray.controls; let i = index">
                <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"
                        formControlName="{{i}}">
                    <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
                        <mat-option [value]="null">{{'CSV.NONE' | translate}}</mat-option>
                        <mat-optgroup *ngFor="let group of filteredOptions[i] | 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]="[group.name] + ' | ' + [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>
        </div>
    </form>
</mat-accordion>

Comments

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.