Riza Khan
Riza Khan

Reputation: 3158

Dynamically add properties to a new class object in Typescript

So I have a api call that brings back information about a user:

data: {
  name: 'Some One',
  age: 100,
  gender: 'male'
  // etc
}

And I'm setting this to a class object.

class User {
  public data: any = {}
  constructor(data: any):void {
    this.updateData(data)
  }

  updateData(data) {
    for (const property in data) {
      if (Object.prototype.hasOwnProperty.call(data, property) &&
         typeof data[property] !== 'function') {
          this.data[property] = data[property]
       }
    }
  }
}

So when you do a const user = new User(userInfoFromApi) and console.log(user), it sends up looking like the same way it came in from the API.

What I want to do is reference user.name instead of user.data.name. Is that possible?

Something I tried was setting

const self = this

And then self[property] = data[property] but Typescript did NOT like that.

Upvotes: 0

Views: 849

Answers (2)

欧阳斌
欧阳斌

Reputation: 2351

interface IUser {
  name: string;
  age: number;
  gender: "male" | "female";
}

class Base<T> {
  public readonly data: T;
  constructor(data: T) {
    this.data = data;
  }

  // i suppose updateData allow update partly
  public updateData(data: Partial<T>) {
    for (const property in data) {
      if (
        Object.prototype.hasOwnProperty.call(data, property) &&
        typeof data[property] !== "function"
      ) {
        this.data[property] = data[property]!;
      }
    }
  }
}

// first way use getter setter
class User extends Base<IUser> implements IUser {
  public get name() {
    return this.data.name;
  }
  public get age() {
    return this.data.age;
  }
  public get gender() {
    return this.data.gender;
  }
  public set name(name) {
    this.data.name = name;
  }
  public set age(age) {
    this.data.age = age;
  }
  public set gender(gender) {
    this.data.gender = gender;
  }
}

const user1 = new User({ name: "", gender: "female", age: 10 });
user1.updateData({ name: "part of value" });
user1.age = 2;
console.log(user1.data); // { name: 'part of value', gender: 'female', age: 2 }
console.log(user1.name); // part of value

// second way use Object.defineProperty
function create<T extends object>(data: T): Base<T> & T {
  const target = new Base(data);
  for (const property in data) {
    if (
      Object.prototype.hasOwnProperty.call(data, property) &&
      typeof data[property] !== "function"
    ) {
      Object.defineProperty(target, property, {
        get: function (this: Base<T>) {
          return this.data[property];
        },
        set: function (this: Base<T>, value) {
          this.data[property] = value;
        },
      });
    }
  }
  return target as never;
}



const user2 = create<IUser>({ name: "", gender: "female", age: 10 });
user2.updateData({ name: "part of value" });
user2.age = 2;
console.log(user2.data); // { name: 'part of value', gender: 'female', age: 2 }
console.log(user2.name); // part of value

Upvotes: 1

JackySky
JackySky

Reputation: 333

You can do (this as any)['some_random_properties'] but this basically bypassed the type checking provided so you have to pretty careful about that.

I would suggest to define the properties exactly in class because that is actually what the type checking is for.

Upvotes: 0

Related Questions