Search code examples
node.jsangularangular7angular8angular-reactive-forms

Angular 8: Dynamically set formControlName in html based on component


In db table, I have "key_name" which is successfully returning in API and working great.

What I want to do is to set those fields value as a formControlName in html and in component I am using the reactive form so I have tried formArrayName.

Based on the change event I am pushing new formControl in categoryArray and assigning its key as db field's value.

here is my Html:

<div class="col-md-6">
                            <div formArrayName="categoryArray" *ngFor="let type of types; let k = index">
                                <div [formGroupName]="k">
                                    <div class="form-group">
                                        <label>Select {{ type.cat_name }}</label>
                                        <select class="form-control custom-select" 
                                            (change)="onChangeType($event.target.value)"
                                            formControlName="{{ type.key_name }}" >
                                            <option value="">-- Select --</option>
                                            <option *ngFor="let type of types" [value]="type.category_id">
                                                {{type.cat_name}}
                                            </option>
                                        </select>
                                    </div>
                                </div>
                            </div>
                        </div>

component.ts:

onChangeCategory(id) {
    if(id) {
      this.artistService.fetchTypeAndSubTypes(id).subscribe(res => {
        if (res.status == 200) {
          this.types = res.data

          this.addInNewCategory(this.types)

        }
      })
    } else {
      this.types = null
      this.subTypes = null
    }
  }

  get category(): FormArray {
    return <FormArray>this.uploadArtWorkForm.controls.categoryArray;
  }

  addInNewCategory(types) {
    let obj = {}

    types.forEach(element => {
      obj[element['key_name']] = [null, [Validators.required]]
    });

    console.log("obj", obj)
    this.category.push(
      this.formBuilder.group(obj)   
    );

  }

What I want to archive is that based on 1st category selection all 2nd level category will be shown as a dynamic HTML dropdown who has their child category listing in the dropdown.

Errors that I am getting:

ERROR Error: Cannot find control with path: 'categoryArray -> 1'

ERROR Error: Cannot find control with path: 'categoryArray -> 1 -> metal'

EDIT: After setting up form control name, How can I set each newly created dropdown's value ? I need to fetch it from the db.

Here is Stackblitz: https://stackblitz.com/edit/dyncamic-populating-dropdown

Thanks in advance


Solution

  • Tried modifying your code myself at least the errors below are fixed:

    ERROR Error: Cannot find control with path: 'categoryArray -> 1'

    ERROR Error: Cannot find control with path: 'categoryArray -> 1 -> metal'

    But you still need to improve on your end using my solution.

    Modify your app.component.html as below:

    <form [formGroup]="uploadArtWorkForm" (ngSubmit)="addUploadArtWorkForm();">
        <div class="col-md-6">
            <div class="form-group">
                <label>Select Category:</label>
          <select class="form-control custom-select" formControlName="cat_id" [ngClass]="{'is-invalid': uploadArtWorkForm.controls['cat_id'].invalid && (uploadArtWorkForm.controls['cat_id'].dirty || uploadArtWorkForm.controls['cat_id'].touched || isFormSubmitted) }" (change)="onChangeCategory($event, $event.target.value)">
            <option value="">-- Select --</option>
            <option *ngFor="let category of parentCategories" [value] ="category.category_id">
                {{category.cat_name}}
            </option>
          </select>
                <div class="invalid-feedback" *ngIf="uploadArtWorkForm.controls['cat_id'].errors">
                    <div *ngIf="uploadArtWorkForm.controls['cat_id'].errors.required">Category is
                        required.</div>
                </div>
            </div>
        </div>
        <div class="col-md-6" formArrayName="categoryArray" *ngIf="uploadArtWorkForm.get('categoryArray')['controls'].length > 0">
            <div  *ngFor="let item of uploadArtWorkForm.get('categoryArray')['controls']; let k = index">
                <div [formGroup]="item">
                    <div class="form-group ">
                        <label>Select {{ types[k].cat_name }}</label>
                        <select class="form-control custom-select" [class]="types[k].key_name" (change)="onChangeType($event.target.value)" formControlName="{{ types[k].key_name }}" *ngIf="types[k].cat_name == 'Type'"> 
                  <option value="">-- Select --</option>
                  <option *ngFor="let type of catTypeValue" [value]="type.category_id">
                      {{type.cat_name}}
                  </option>
              </select>
              <select class="form-control custom-select" [class]="types[k].key_name" (change)="onChangeType($event.target.value)" formControlName="{{ types[k].key_name }}" *ngIf="types[k].cat_name == 'Metal'">
                  <option value="">-- Select --</option>
                  <option *ngFor="let type of catMetalValue" [value]="type.category_id">
                      {{type.cat_name}}
                  </option>
              </select>
              <select class="form-control custom-select" [class]="types[k].key_name" (change)="onChangeType($event.target.value)" formControlName="{{ types[k].key_name }}" *ngIf="types[k].cat_name == 'Gems'">
                  <option value="">-- Select --</option>
                  <option *ngFor="let type of catGemsValue" [value]="type.category_id">
                      {{type.cat_name}}
                  </option>
              </select>
                    </div>
                </div>
            </div>
        </div>
    </form>
    

    and app.component.ts as below:

    import { Component } from "@angular/core";
    import { FormGroup, FormBuilder, Validators, FormArray, FormControl } from "@angular/forms";
    
    @Component({
      selector: "my-app",
      templateUrl: "./app.component.html",
      styleUrls: ["./app.component.css"]
    })
    export class AppComponent {
      uploadArtWorkForm: any;
      uploadArtWorkForm1: any;
      isFormSubmitted: boolean = false;
    
      parentCategories = [
        {"parent_id":0,"cat_name":"Jewelry","category_id":1},
        {"parent_id":0,"cat_name":"Painting","category_id":16},
        {"parent_id":0,"cat_name":"Photograph","category_id":22}];
      types;
      subTypes;
    
      //below value will be displayed in Select Type dropdown
      catTypeValue = [
        {"parent_id":2,"cat_name":"Anklets","category_id":3,"key_name":"anklets"},
        {"parent_id":2,
        "cat_name":"Body",
        "category_id":4,
        "key_name":"body"},
        {"parent_id":2,"cat_name":"Bracelets","category_id":5,
        "key_name":""},
        {"parent_id":2,
        "cat_name":"Charms","category_id":6,
        "key_name":""},
        {"parent_id":2,"cat_name":"Necklaces","category_id":7,
        "key_name":""},
        {"parent_id":2,"cat_name":"Ornamental","category_id":8,
        "key_name":""},
        {"parent_id":2,
        "cat_name":"Rings & Studs","category_id":9,
        "key_name":""},
        {"parent_id":2,
        "cat_name":"Jewelry Metal","category_id":10,"key_name":""},{"parent_id":2,
        "cat_name":"Yellow Gold","category_id":11,
        "key_name":""},
        {"parent_id":2,
        "cat_name":"White Gold","category_id":12,"key_name":"whitegold"},{"parent_id":2,
        "cat_name":"Silver","category_id":13,"key_name":"silver"}
        ]
    
      // below values will be displayed in Select Metal dd
      catMetalValue = [
        {"parent_id":14,"cat_name":"White Gold","category_id":24,"key_name":"whitegold"},
        {"parent_id":14,"cat_name":"Yellow Gold","category_id":24,"key_name":"whitegold"},
        ]
    
      // below values will be displayed in Select Gems dd
      catGemsValue = [
        {"parent_id":15,"cat_name":"Blue Gems","category_id":25,"key_name":"bluegems"}
        ]
    
      constructor(private formBuilder: FormBuilder) {
        this.uploadArtWorkForm = this.formBuilder.group({
        cat_id: new FormControl('', Validators.required),
        categoryArray: this.formBuilder.array([]),
      });
      }
      ngOnInit() {}
      onChangeType(value, id){
        console.log(value)
    // solution to https://stackoverflow.com/questions/61315779/angular-8-not-getting-selected-dropdown-values-in-component-using-reactive-form?noredirect=1&lq=1
      }
      onChangeCategory(event, id) {
        if (id) {
          this.types = [
            {"parent_id":1,
            "cat_name":"Type",
            "category_id":2,
            "key_name":"type"},
            {"parent_id":1,
            "cat_name":"Metal",
            "category_id":14,
            "key_name":"metal"},
            {"parent_id":1,
            "cat_name":"Gems",
            "category_id":15,
            "key_name":"gems"}];
          if(this.uploadArtWorkForm.controls['categoryArray'].length > 0){
            this.uploadArtWorkForm.controls['categoryArray'].controls.length = 0
          }
          this.addInNewCategory(this.types);
        } else {
          this.types = null;
          this.subTypes = null;
        }
      }
    
      get category(): FormArray {
        return <FormArray>this.uploadArtWorkForm.controls['categoryArray'];
      }
    
      addInNewCategory(types) {['categoryArray'];
        types.forEach((element, i) => {
          this.category.push(this.formBuilder.group({[element["key_name"]]: new FormControl(element["key_name"])
          }));
        });
      }
    }