Search code examples
javascriptangulartypescriptangular-ngmodel

Dynamically binding to nested object property


I am trying to bind to a property of a nested object with [(ngModel)], but the problem is that the "path" to property is dynamically set.

Let's say we have three classes like so:

class A {
 p1: C
 constructor() {
  p1 = new C("A")
 }
}
class B {
 p2: C
 constructor() {
  p2 = new C("B")
 }
}
class C {
 constructor(public id: string){}
}

reusable.component.html like this:

<input [(ngModel)]="data[propName]">

reusable.component.ts like this:

@Component({
  selector: "app-reusable[data][nestedProperty]",
  templateUrl: "./reusable.component.html"
})
export class ReusableComponent<T> {
 @Input()
 nestedProperty!: string
 @Input()
 data!: T
}

and app.component.html like so:

<app-reusable [data]="d1" [nestedProperty]="s1"></app-reusable>
<app-reusable [data]="d2" [nestedProperty]="s2"></app-reusable>

and app.component.ts like so:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
d1 = new A()
d2 = new B()
s1 = "p1.id"
s2 = "p2.id"
}

my logic for this kind of approach was that d1.p1 gets the same value as d1["p1"], but i found out that d1["p1.id"] does't get the same value as d1.p1.id

Is there any kind of workaround that would enable me to fetch a nested object value using only string path to the nested object?

My workaround was to create property d1["p1.id"] inside the constructor and assign it d1.p1.id value like so:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
d1 = new A()
d2 = new B()
s1 = "p1.id"
s2 = "p2.id"

 constructor() {
  this.d1[s1] = this.d1.p1.id
  this.d2[s2] = this.d2.p2.id
 }
}

Solution

  • If you know the path to get the property You can use eval to evaluate the expression to access it:

    The eval() function evaluates JavaScript code represented as a string

    If you have an object as

    var myObject = {
       innerObject: {
          nestedProperty: 'The value'
       }
    };
    

    You can get the string 'The value' doing

    var nestedValue = eval("myObject.innerObject.nestedProperty");
    // Here nestedValue has the value 'The value'
    

    Note that the use of eval can be dangerous because it will execute any kind of string as it is a code, so a better way to solve the problem in this case is to use the Function as explained here

    function extractProperty(path) {
       return Function('"use strict";return (' + path + ')')();
    }
    

    and use it as follow for the previous example:

    var nestedValue = extractProperty("myObject.innerObject.nestedProperty");
    

    This is a standard javascript code, but you can adapt it to your typescript needs.