Thomas Riley
Thomas Riley

Reputation: 409

Angular 8 ngFor's keyvalue pipe and ngModels. Having issues binding values

I'm trying to get this component working that will take an object of key/value pairs, display them in input tags, and change them on the original object. So say I have an object like:

{
    name: "John",
    id: "12345",
    preferences: { foo: "bar", biz: "baz" }
}

I can call <key-value [obj]="profile.preferences"></key-value> and the data will be printed in two pairs in input tags. If I then changed "foo" to "qux" the property key would change. To accomplish this I have a key-value component that really simply does:

<div *ngFor="let pref of obj | keyvalue">
     <input [(ngModel)]="pref.key"><br />
     <input [(ngModel)]="pref.value"><br />
</div>

Which I feel is very simple. It's an object passed in, so it's a reference to the original object, and the ngModels in literally every other input of my application work so I assume that's not wrong.

I've done a lot of looking around and I did actually get it working if I had a "changeKey/changeValue" function that would change this.obj in the component rather than relying on binding:

<div *ngFor="let pref of obj | keyvalue">
     <input (change)="changeKey(/*???*/)" [ngModel]="pref.key"><br />
     <input (change)="changeValue(pref.key, pref.value) [ngModel]="pref.value"><br />
</div>

That works for the changeValue because I have the key I need to change and the new value. But the issue with the changeKey is because pref.key has been changed I don't know which key to update.

I feel like this shouldn't be that difficult, I'm still quite new to Angular and I'm hoping someone here knows what's going on. Thanks.

Upvotes: 8

Views: 9111

Answers (3)

user1689987
user1689987

Reputation: 1556

I needed to do it like this for example:

 <span *ngIf="hasProp(subChart.series, 'params')" >
        <mat-form-field class="example-full-width" *ngFor="let keyVal of subChart.series.params |  keyvalue">
          <mat-label>{{keyVal.key}}</mat-label>
          <input matInput type="number" [(ngModel)]="subChart.series.params[castToString(keyVal.key)]"
                 [value]="subChart.series.params[castToString(keyVal.key)]" />
        </mat-form-field>
      </span>

Which uses these functions:

  public castToString(obj: any): string{
    return obj as string;
  }
  public hasProp(obj: any, name: string) {
    return obj.hasOwnProperty(name);
  }

Upvotes: 0

Shaya
Shaya

Reputation: 2932

KeyValuePipe in a nutshell:

Transforms Object or Map into an array of key value pairs.

The keyvalue pipe creates a new array from a given object, and as such, binding to it will not affect the original object.

As for your issue, instead of using the (change) event listener, you can bind ngModel to your original object and point to it directly:

<div *ngFor="let prop of myObject | keyvalue">
      <input [(ngModel)]="myObject[prop.key]">
</div>

Upvotes: 11

Samuel
Samuel

Reputation: 462

You can define the changeKey function like the following code.

In Component

changeKey(prevKey, newKey) {
  const value = this.obj[prevKey];
  delete this.obj[prevKey];
  this.obj[newKey] = value;
}

In HTML

<input (change)="changeKey(pref.key, $event.target.value)" [ngModel]="pref.key"><br />

I created a working example on stackblitz.

Upvotes: 4

Related Questions