2

I am new to angular, currently using Angular v.8. I have a json map in my component. I want to use this map to create my input fields (key as field label and value as the input value) in html. I have iterated this map and populated the fields based on the json map data, but editing the value is not updating the actual json map. I need the updated map back in the component to send to backend API's. Please help, code snippets below:

component: .. mapRecord: any = {"Col1":"Val1", "Col2":"Val2", "Col3":""} ...

html:

<table>
    <tr *ngFor="let record of mapRecord | keyvalue">
        <td>
            <div [formGroup]="addGlimpseForm">
                <mat-form-field> <input matInput
                    placeholder="{{record.key}}" [(ngModel)]="record.value"
                    formControlName="{{record.key}}" name="record.value" id="record.value">
            </mat-form-field>
            </div>
        </td>
    </tr>
</table>
{{ mapRecord | json }}

EDIT: I already have the form control populated dynamically at component:

 glimpseFields: string[] = [];
..
    for (var key in this.mapRecord) {
      this.glimpseFields.push(key);
    }
    this.addGlimpseForm = new FormGroup({username: new FormControl('', [])});
    for (let glimpseField of this.glimpseFields) {
      this.addGlimpseForm.addControl(glimpseField, new FormControl('', Validators.required));
    }
1
  • I see [formGroup]="addGlimpseForm" which indicates a reactive form and [(ngModel)] which indicates a template driven form. Don't mix. Commented Sep 6, 2019 at 13:17

3 Answers 3

2

The reason that the mapRecord is not updating is because of the pipe you are using in your *ngFor (i.e. mapRecord | keyvalue). This pipe returns a new object ([ { "key": "Col1", "value": "Val1" }, ... ]) and so the object reference to mapRecord is lost. You need to either use a reactive form to handle the value submission or map the values to an array in your component and then map them back before you submit.

To solve the issue I would just map the mapRecord to an array of all of the fields in ngOnChanges (or wherever it makes sense so that if the list of fields changes the logic will run again).

Using template forms whenever the onSubmit function is called the form's value will automatically translate it back into an object that you can use to submit the data.

Component:

export class Example implements OnChanges {
  @Input() public mapRecord?: any;

  public formFields: { key: string, value: string }[] = this.toFormFields({
    "Col1": "Val1",
    "Col2": "Val2",
    "Col3": "",
  });

  ngOnChanges() {
    this.formFields = this.toFormFields(this.mapRecord);
  }

  private toFormFields(data: any) {
    return Object.keys(data).map(key => ({ key, value: data[key] }));
  }

  public onSubmit(form: NgForm) {
    this.mapRecord = form.value;
    // Do submit logic
  }
}

Template:

<form (ngSubmit)="onSubmit(form)" #form="ngForm">
  <table>
    <tr *ngFor="let record of formFields">
      <td>
        <div>
          <mat-form-field>
            <input matInput
              [placeholder]="record.key"
              [(ngModel)]="record.value"
              [name]="record.key"
              [id]="record.value" />
          </mat-form-field>
        </div>
      </td>
    </tr>
  </table>
  <button type="submit" name="submit">Submit</button>
</form>

Stackblitz

Sign up to request clarification or add additional context in comments.

3 Comments

which is the best way? Also, can you please provide the implementation details, as I'm new to angular.
I tried to avoid the pipe by using key object as @Ronak mentioned, but still same error. Could you please help? keys : any; this.keys = Object.keys(this.mapRecord); <table> <tr *ngFor="let key of keys"> <td> <div [formGroup]="addGlimpseForm"> <mat-form-field> <input matInput placeholder="{{key}}" [(ngModel)]="mapRecord[key]" formControlName="{{key}}" name="mapRecord[key]" id="mapRecord[key]"> </mat-form-field> </div> </td> </tr> </table>
See my updated answer. Note this is just one way to do it but there may be a better way depending on your app.
1

Hi you have to do few change for getting key and it's value. you have to create object for key.

mapRecord : any;
keys : any;
constructor(){
     this.mapRecord  =  {"Col1":"Val1", "Col2":"Val2", "Col3":""};
     this.keys = Object.keys(this.mapRecord);
}

Below you need to update table.

        <table>
    <tr *ngFor="let key of keys">
                <td>
                    <div [formGroup]="addGlimpseForm">
                        <mat-form-field> <input matInput
                            placeholder="{{key}}" [(ngModel)]="mapRecord[key]"
                            formControlName="{{key}}" name="mapRecord[key]" id="mapRecord[key]">
                    </mat-form-field>
                    </div>
                </td>
            </tr>
</table>

Below if example which help you.

https://stackblitz.com/edit/angular-13xuze

Please look into above changes and let me know if you have any query. Thanks.

2 Comments

@ShihadSalam Can you show me where you have facing issue ?
Initial problem still persists. the mapRecord values are not updating while editing the input field. Updated text input value is not reflecting in the actual map object, mapRecord. record.value is showing the updated value, but not the mapRecord. As Teddy mentioned, I am losing the map reference here while using pipe. But don't know how to proceed here. Tried you method as well, but still no luck !
1

Component: test.component.ts


import { Component, Input, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {
  @Input() public mapRecord?: any;

  public FormColumns: { key: string, value: string }[] = this.FormColumn({
    "Col1": "Val1",
    "Col2": "Val2",
    "Col3": "",
  });

  ngOnInit() {
    this.FormColumns = this.FormColumn(this.mapRecord);
  }

  private FormColumn(data: any) {
    return Object.keys(data).map(key => ({ key, value: data[key] }));
  }

  public onSubmit(form: NgForm) {
    this.mapRecord = form.value;
    // Do submit logic
  }

}

Template: test.component.html


    <form (ngSubmit)="onSubmit(recordForm)" #recordForm="ngForm">
    <table>
        <tr *ngFor="let formdata of FormColumns">
            <td>
                <div>
                    <mat-form-field>
                        <input matInput
              [placeholder]="formdata.key"
              [(ngModel)]="formdata.value"
              [name]="formdata.key"
              [id]="formdata.value" />
          </mat-form-field>
        </div>
      </td>
    </tr>
  </table>

    <button type="submit" name="submit">Submit</button>
  </form>
  <br>
  FormColumns: <pre>{{ FormColumns | json }}</pre>
  mapColumn: <pre>{{ mapRecord | json }}</pre>

Stackblitz

3 Comments

already populated the form group in component, still same issue.
@ShihadSalam can you show me where you have facing issue ?
Not fair to copy the answer from @Teddy Sterne !

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.