typescriptintellisense

Is it possible in typescript to have a function return the type of a class that extends it


export class Base {
    static getSomething():typeof this //<-- change this?
    {
        var type = typeof this;
        return new this['type']();
    }
}
export class Foo extends Base {
    publicVar:string = 'getThis!';
}

var foo:Foo = Foo.getSomething();
var success:string = foo.publicVar;

the above returns an error because the compiler says Foo.getSomething will return a Base, not a Foo. I wonder if there is another way that lets me call a static function of Foo that the compiler knows will return a Foo.

Ofcourse I can implement it explicitly in Foo, and thus in every class that extends Base, or I could simply typecast it:

var foo:Foo = <Foo> Foo.getSomething();

But I was wondering if there is a way without having to do either of those things, since I'll be using this method a lot


Solution

  • Getting Rid Of The Type Assertion

    If you follow the Liskov substitution principle, you don't need the type assertion.

    Code Example 1 - Substitutable Objects...

    module Test {
        export class Base {
           publicVar: string = 'base';
    
           static getSomething() : Base  {
                //... read on...
           }
        }
    
        export class Foo extends Base {
            publicVar:string = 'getThis!';
        }
    }
    
    // No <Foo> type assertion needed
    var foo: Test.Foo = Test.Foo.getSomething();
    
    alert(foo.publicVar);
    

    Alternatively, you could create an interface that tells you the object returned will have a publicVar property and return that...

    Code Example 2 - Interface

    module Test {
        export interface IPublicVarable {
            publicVar: string;
        }
    
        export class Base {
           static getSomething() : IPublicVarable  {
                //... read on...
           }
        }
    
        export class Foo extends Base {
            publicVar:string = 'getThis!';
        }
    }
    
    // No <Foo> type assertion needed
    var foo: Test.IPublicVarable = Test.Foo.getSomething();
    
    alert(foo.publicVar);
    

    Getting the Actual Type

    This doesn't solve one other issue you have though - var type = typeof this; isn't going to give you what you expect at runtime. It is going to give you Function not Foo.

    To get a type name, you really need to work with an instance (if you use Test.Foo the type name is Function once again - which does you no good), so here is an imperfect example using two different subclasses that both satisfy the interface, based on my Obtaining a Class Name at Runtime example:

    module Test {
        export class Describer {
            static getName(inputClass) { 
                var funcNameRegex = /function (.{1,})\(/;
                var results = (funcNameRegex).exec((<any> inputClass).constructor.toString());
                return (results && results.length > 1) ? results[1] : "";
            }
    
            static getInstanceOf(inputClass) : Test.IPublicVarable {
                var name = Describer.getName(inputClass);
                return new Test[name]();
            }
        }
    
        export interface IPublicVarable {
            publicVar: string;
        }
    
        export class Base {
    
        }
    
        export class Foo extends Base {
            publicVar:string = 'foo class';
        }
    
        export class Bar extends Base {
            publicVar:string = 'bar class';
        }
    }
    
    var a: Test.Base = new Test.Foo();
    var x: Test.IPublicVarable = Test.Describer.getInstanceOf(a);
    alert(x.publicVar);
    
    var b: Test.Base = new Test.Bar();
    var y: Test.IPublicVarable = Test.Describer.getInstanceOf(b);
    alert(y.publicVar);