Search code examples
angulartypescriptngforangular-ngmodel

How to access members of object in ngModel by string keys


I am trying to generate input fields programmatically because there are a whole lot of them.
Unfortunately their database names are != to the display names. Therefor i can't just pipe | keyvalue.

Given

user : customer = {
    FirstName: "",
    LastName: "",
    ID: 0,
    Phone: "",
    ...
};
fields = [
    {name: "Vorname", value: "FirstName" as keyof customer},
    {name: "Nachname", value: "LastName" as keyof customer},
    {name: "Handynummer", value: "Phone" as keyof customer},
    ...];

How can I connect the inputs of the *ngFor to the ngModel ?

<mat-form-field *ngFor="let item of fields;">
  <mat-label>{{item.name}}</mat-label>
  <input matInput [(ngModel)]="user[item.value]">
</mat-form-field>

The error in Angular is "Type 'any' is not assignable to type 'never'." But I don't get it, because the mebers of customer are never never.

I tried to cast inside the ngModule or before in the *ngFor, but i didn't find a solution that run in neither. Google dind't help, i dont really know what keywords I need. I found some questions from 2016 and 2017 but those errors where caused by double named form fields.


Solution

  • One possible cause of this error is that the type of the user object is not being inferred correctly due to its declaration, it is presumed as type NEVER while field.value which is of type ANY can hold a value that does not correspond to any of the user object keys. the dynamic parsing also makes it impossible for typescript to establish any relationship between both objects.

    Here are few ways to achieve this:

    1. add an index signature to the customer interface to allow for dynamic property access using string keys:
       FirstName: string;
       LastName: string;
       Phone: string;
       ...
       [key: string]: any;
       } 
    

    Without an index signature, TypeScript does not recognize that the user object can have additional properties with string keys, which is why it needs the index signature to allow for dynamic property access. Once added, TypeScript knows that the user object can have additional properties with string keys, and we can access those properties with field.value as in the ngModel binding.

    1. A longer shot, not efficient for large data/objects: Combine both objects extracting labels and values as new objects pushed into an array, loop through the array in the mat-form-field markup.

    2. Create a child component for the input field, extract the input and label values on the parent.ts respectively as explained here no-index-signature pass them as Input() properties to the child component, and listen to changes via Event Emitter.