Search code examples
angularangular-materialangular-forms

How to show a default value in a mat-select of a dynamic form?


I am updating a project to Angular 14 with Angular material, I have generated a generic form with several fields, but the mat-select does not keep the default option.

I am generating the dynamic FormControl from the component.ts, the text fields if the value is shown, but the mat-select is not.

This is my component.html:

    <div  [formGroup]="formDialog" mat-dialog-content *ngFor="let column of data.columns">
        <mat-form-field *ngIf="column != 'province'">
            <input matInput [formControlName]="column" [placeholder]="column">
        </mat-form-field>
        <mat-form-field *ngIf="column == 'province'">
            <mat-label>{{ column }}</mat-label>
            <mat-select [formControlName]="column" (valueChange)="currProvince($event)"> 
                <mat-option *ngFor="let item of geoProvinces" [value]="item">
                    {{ item.nm }}
                </mat-option>
            </mat-select>
        </mat-form-field>
    </div>

This is my component.ts:

    ////////////
    this.data.dataRow = {
       "name":"test3Prov",
       "phoneNumber":123456,
       "province":"Madrid"
    }
    //////////////

    ngOnInit(): void {

        this.getGeoAPI();

        let fieldControls = {};
        Object.entries(this.data.dataRow).forEach(([key, value]) => {
            fieldControls[key] = new FormControl(value || '');
        })
        this.formDialog = new FormGroup(fieldControls)
    }

This is the method where the provinces are obtained:

    getGeoAPI(): void {
        this.genericService.getPath('geoApi').subscribe((elem: any) => {
          this.geoProvinces = elem.provinces
        })
      }

This is an example of this.geoProvinces:

    [
       {
          "id":"01",
          "nm":"Álava"
       },
       {
          "id":"02",
          "nm":"Albacete"
       },
       {
          "id":"03",
          "nm":"Alicante"
       }
    ]

This is a screenshot with the form shown:

enter image description here


Solution

  • The problem is that your mat-select is feed with an array of object and your formControl value is an string

    I imagine you can use in your mat-select as value "item.nm" (or what-ever property you has in your array)

    <mat-option *ngFor="let item of geoProvinces" [value]="item.nm">
          {{ item.nm }}
    </mat-option>
    

    BTW if we can store an object we should use in the option [ngValue] instead [value]

    Another option is that really you want to store in the formControl the "object", in this case you can add in your ngOnInit

    Object.entries(this.data.dataRow).forEach(([key, value]) => {
         ...
    })
    
    this.formDialog = new FormGroup(fieldControls
    const control=this.formDialog.get('province')
    if (control && control.value)
    {
       const province=this.geoProvinces.find(x=>x.nm==control.value)
       control.setValue(province?province:null)
    }
    

    Update

      //The array geoProvince is in the way 
         [{"id":"01","nm":"Álava"} ..]
      //And our data in the way 
         {"name":"test3Prov",
          "phoneNumber":123456,
          "province":"Madrid"
         }
    

    So first we need think what we can store in the FormControl "province"

    We can store the "name of the province"

    const fieldControls: any = {};
    Object.entries(this.data.dataRow).forEach(([key, value]) => {
        fieldControls[key] = new FormControl(value || '');
    })
    this.formDialog = new FormGroup(fieldControls)
    
    //the ng-select like -see mat-option [value]="item.nm"
    <mat-select [formControlName]="column"(selectionChange)="currProvince($event)"> 
            <mat-option *ngFor="let item of geoProvinces" 
                 [value]="item.nm">
                {{ item.nm }}
            </mat-option>
        </mat-select>
    

    We can store the "id" of the province

    const fieldControls: any = {};
    Object.entries(this.data.dataRow).forEach(([key, value]) => {
        //we check if we are creating the formControl province
        //if true we asing the "id"
        if (key=="province"){
          const prov=this.geoProvinces.find(x=>x.nm==value)
          fieldControls[key] = new FormControl(prov?prov.id:null || '');
        }
        else
           fieldControls[key] = new FormControl(value || '');
    })
    this.formDialog = new FormGroup(fieldControls)
    
    //the ng-select like -see mat-option [value]="item.id"
    <mat-select [formControlName]="column"(selectionChange)="currProvince($event)"> 
            <mat-option *ngFor="let item of geoProvinces" 
                 [value]="item.id">
                {{ item.nm }}
            </mat-option>
        </mat-select>
    

    We can store the whole object. We need take account that if we store an object or we asign the object (search in the array geoProvinces) or we using the property "compareWith"

    Store the whole object search in the array geoProvinces

    const fieldControls: any = {};
    Object.entries(this.data.dataRow).forEach(([key, value]) => {
        //we check if we are creating the formControl province
        //if true we asing the element
        if (key=="province"){
          const prov=this.geoProvinces.find(x=>x.nm==value)
          fieldControls[key] = new FormControl(prov?prov:null || '');
        }
        else
           fieldControls[key] = new FormControl(value || '');
    })
    this.formDialog = new FormGroup(fieldControls)
    
    //the ng-select like -see mat-option [value]="item"
    <mat-select [formControlName]="column"(selectionChange)="currProvince($event)"> 
            <mat-option *ngFor="let item of geoProvinces" 
                 [value]="item">
                {{ item.nm }}
            </mat-option>
        </mat-select>
    

    Using "compareWith"

    const fieldControls: any = {};
    Object.entries(this.data.dataRow).forEach(([key, value]) => {
        if (key=="province"){ //create "on-fly" and object"
           fieldControls[key] = new FormControl({nm:value || ''});
        else
          fieldControls[key] = new FormControl(value || '');
    })
    this.formDialog = new FormGroup(fieldControls)
    
    //the ng-select like -see mat-option [value]="item"
    
    //see also the [compareWith]="compareProvince"
    <mat-select [formControlName]="column"
            [compareWith]="compareProvince"
            (selectionChange)="currProvince($event)"> 
            <mat-option *ngFor="let item of geoProvinces" 
                 [value]="item">
                {{ item.nm }}
            </mat-option>
        </mat-select>
    
    //and we declare compareProvince in .ts
    compareProvince = (a: any, b: any) => a.nm == b.nm;
    

    See the stackblitz