148

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();
basarat
  • 261,912
  • 58
  • 460
  • 511
  • 2
    For older typescript (<1.6), see [Any way to nest classes in typescript?](http://stackoverflow.com/questions/13495107/any-way-to-nest-classes-in-typescript) – Rahul Tripathi Sep 10 '15 at 06:03

6 Answers6

215

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();
basarat
  • 261,912
  • 58
  • 460
  • 511
  • 2
    this example doesn't work in Typescript 1.6.3: Error TS4028 Public static property 'Bar' of exported class has or is using private name '(Anonymous class)'. – Sergey Oct 23 '15 at 12:23
  • 1
    @Sergey I had the same problem so started using namespaces as per my answer below. – Dan Def Sep 29 '16 at 10:57
  • 1
    Is there a way to create interface inside a class? i.e. if we want that Bar will be an interface – RoG Oct 10 '18 at 12:03
  • @LittaHervi - One use case would be returning a private implementation of an external interface from, for example, a factory method. Another example from Java (and others) are iterators that require privileged access to member variables of their containing class and through scoping rules automatically do so, but you never want these to be exported and instantiated directly. – Dave Nov 26 '19 at 05:57
  • This allows you to construct a new instance yes but it can't be used as a type far as I can tell. – Emperor Eto Jun 24 '21 at 11:50
  • 5
    Any way to reuse the nested class from the parent class? class Foo { static Bar = class { }; fieldA: Bar; //doesn't work: Cannot find name 'Bar' fieldB: Foo.Bar; //doesn't work: 'Foo' only refers to a type, but is being used as namespace here. } // works! var foo = new Foo(); var bar = new Foo.Bar(); – LucasM Nov 16 '21 at 16:01
  • 3
    This doesn't let you use the inner type in type annotations. – interjay Nov 01 '22 at 21:17
  • I just learned that you can't use property decorators with this syntax :( – Jimmie Tyrrell Dec 11 '22 at 04:25
61

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

bnieland
  • 6,047
  • 4
  • 40
  • 66
23

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 { }
}
Lee Goddard
  • 10,680
  • 4
  • 46
  • 63
Dan Def
  • 1,836
  • 2
  • 21
  • 39
17

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();
danvk
  • 15,863
  • 5
  • 72
  • 116
5

This answer is about a seamless implementation of nested classes.

Defining Foo.Bar (static)

Defining the static nested class Foo.Bar can be done in the following two ways.

  1. Option 1: Defining the nested class Bar inside the class Foo. The type is declared in a declare namespace declaration.
  2. Option 2: Using declaration merging to define the nested class Bar inside a Foo namespace with the export keyword.
Option 1: Implementing Bar inside class Foo

All of the classes are implemented in one block, namely in that of the Foo class declaration.

class Foo {
    static Bar = class { }
}

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

let bar: Foo.Bar = new Foo.Bar()
Option 2: Implementing Bar inside namespace Foo

For static classes, the following implementation might be more elegant to some. The downside is that this method does not work with non-static nested classes.

class Foo { }

namespace Foo {
    export class Bar { }
}

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

Defining Foo.prototype.Bar (non-static)

To create a seamless non-static nested class, one may use prototype to indicate that the nested class is not static.

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()

Note: the call new Foo.prototype.Bar() doesn't work, although it is valid Typescript even without type declaration.

Warnio
  • 111
  • 1
  • 6
0

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

Mohab
  • 11
  • 1