tris
tris

Reputation: 1049

Access dynamic / generic classes' properties in Typescript

I'm using this (complexity super reduced) base model in Typescript:

export class ModelBase<T> {
    constructor(base: T) {
        Object.assign(this, base);
    }
}

If I now try to use it as follows:

interface Model {
    prop: string;
}

class MyClass {
    constructor() {
        let instance = new ModelBase<Model>({ prop: 'test' });
        instance.prop = 'x';
    }
}

Typescript tells me there is no property prop in my instance. How to get rid of it?

Upvotes: 1

Views: 719

Answers (2)

Jeff Bowman
Jeff Bowman

Reputation: 95654

microsoft/TypeScript#2225, originally filed in 2015, requests similar cases where a class or interface extends a generic type. The discussion was closed, though some commenters seem to maintain at the end that the type intersection recommendations do not meet their needs.

As such, one way to do this is to make a static factory method that returns a type intersection of ModelBase<T> & T:

export class ModelBase<T> {
    static wrap<T>(base: T) {
        return new ModelBase<T>(base) as ModelBase<T> & T;
    }

    constructor(base: T) {
        Object.assign(this, base);
    }
}

Playground Link

Naturally, the function could easily be top-level or adjacent to the class rather than within it Java-style. Also, bear in mind that like with everything TypeScript you'll have an imprecise reflection of which properties are enumerable own properties (see #9726), which might be a signficantly narrower set than what you and TypeScript would expect to be available on ModelBase<Foo>. Conversely, you may copy more at runtime than TS can expect at compile-time, and ModelBase instances may have their methods and fields hidden if you have name collisions between ModelBase and whichever T types you're copying: TypeScript will only show you the inferred compile-time interactions rather than any unexpected enumerable runtime properties that Object.assign will copy for you.

Alternatives that don't work:

Upvotes: 2

Alireza Ahmadi
Alireza Ahmadi

Reputation: 9903

On way is that you add parameter in type T to ModelBase like this:

export class ModelBase<T> {
    param!:T;
    constructor(base: T) {
        Object.assign(this.param, base);
    }
}


interface Model {
    prop: string;
}

class MyClass {
    constructor() {
        let instance = new ModelBase<Model>({ prop: 'test' });
        instance.param.prop = "x";
    }
}

PlaygroundLink

Upvotes: 0

Related Questions