Can you create nested classes in TypeScript?

JavascriptTypescript

Javascript Problem Overview


Is there a way to nest classes in TypeScript. E.g. I'd like to use them like:

var foo = new Foo();
var bar = new Foo.Bar();

Javascript Solutions


Solution 1 - Javascript

In modern TypeScript we have class expressions which you can use to create a nested class. For example you can do the following :

class Foo {
	static Bar = class {
		
	}
}

// works!
var foo = new Foo();
var bar = new Foo.Bar();

Solution 2 - Javascript

Here is a more complex use case using class expressions.

It allows the inner class to access the private members of the outer class.

class classX { 
    private y: number = 0; 

    public getY(): number { return this.y; }
    
	public utilities = new class {
		constructor(public superThis: classX) {
		}
		public testSetOuterPrivate(target: number) {
            this.superThis.y = target;
		}
	}(this);    
}

const x1: classX = new classX();
alert(x1.getY());

x1.utilities.testSetOuterPrivate(4);
alert(x1.getY());

codepen

Solution 3 - Javascript

I couldn't get this to work with exported classes without receiving a compile error, instead I used namespaces:

namespace MyNamespace {
    export class Foo { }
}

namespace MyNamespace.Foo {
    export class Bar { }
}

Solution 4 - Javascript

If you're in the context of a type declaration file, you can do this by mixing classes and namespaces:

// foo.d.ts
declare class Foo {
  constructor();
  fooMethod(): any;
}

declare namespace Foo {
  class Bar {
    constructor();
    barMethod(): any;
  }
}

// ...elsewhere
const foo = new Foo();
const bar = new Foo.Bar();

Solution 5 - Javascript

This answer is about a seemless nested class implementation in TypeScript which builds on top of @basarat 's answer.

To make the type of the static nested class Bar accessible (as @PeterMoore pointed out), declare the type of the nested class in a namespace. That way, we can use the shortcut Foo.Bar. By moving type typeof Foo.Bar.prototype into a type in a declared namespace, we do not have to repeat the expression.

class Foo {
    static Bar = class {
        
    }
}

declare namespace Foo {
    type Bar = typeof Foo.Bar.prototype
}

// Now we are able to use `Foo.Bar` as a type
let bar: Foo.Bar = new Foo.Bar()

For static classes, the following implementation might be more elegant. This, however does not work with non-static classes.

class Foo { }

namespace Foo {
    export class Bar { }
}

let bar: Foo.Bar = new Foo.Bar()

To export the class, an export statement can be added after the class and the namespace have been declared, e.g. export default Foo or export { Foo }.

> To achieve the same with a non-static nested class, see the following example. > > > class Foo { > Bar = class { > > } > } > > declare namespace Foo.prototype { > type Bar = typeof Foo.prototype.Bar.prototype > } > > let foo: Foo = new Foo() > let bar: Foo.prototype.Bar = new foo.Bar() >

Solution 6 - Javascript

I Hope this can be helpful

Able to:

  • Create a new inner class instance
  • Access outer class instance/prototype members
  • Implement interfaces
  • Use decorators

Use Case

export interface Constructor<T> {
    new(...args: any[]): T;
}

export interface Testable {
    test(): void;
}

export function LogClassName<T>() {
    return function (target: Constructor<T>) {
        console.log(target.name);
    }
}

class OuterClass {
    private _prop1: string;

    constructor(prop1: string) {
        this._prop1 = prop1;
    }

    private method1(): string {
        return 'private outer method 1';
    }

    public InnerClass = (
        () => {
            const $outer = this;

            @LogClassName()
            class InnerClass implements Testable {
                private readonly _$outer: typeof $outer;

                constructor(public innerProp1: string) {
                    this._$outer = $outer;
                }

                public test(): void {
                    console.log('test()');
                }

                public outerPrivateProp1(): string {
                    return this._$outer._prop1;
                }
                
                public outerPrivateMethod1(): string {
                    return this._$outer.method1();
                }
            }
            return InnerClass;
        }
    )();
}

const outer = new OuterClass('outer prop 1')
const inner = new outer.InnerClass('inner prop 1');

console.log(inner instanceof outer.InnerClass); // true 
console.log(inner.innerProp1); // inner prop 1
console.log(inner.outerPrivateProp1()); // outer prop 1
console.log(inner.outerPrivateMethod1()); // private outer method 1

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbasaratView Question on Stackoverflow
Solution 1 - JavascriptbasaratView Answer on Stackoverflow
Solution 2 - JavascriptbnielandView Answer on Stackoverflow
Solution 3 - JavascriptDan DefView Answer on Stackoverflow
Solution 4 - JavascriptdanvkView Answer on Stackoverflow
Solution 5 - JavascriptWarnioView Answer on Stackoverflow
Solution 6 - JavascriptMohabView Answer on Stackoverflow