168

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

6 Answers 6

237

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();
Sign up to request clarification or add additional context in comments.

9 Comments

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 I had the same problem so started using namespaces as per my answer below.
Is there a way to create interface inside a class? i.e. if we want that Bar will be an interface
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();
This doesn't let you use the inner type in type annotations.
|
70

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

6 Comments

@RyanCavanaugh Is the ability to access private members inside an "inner" class expression a bug?
I verified this is correct usage with the TypeScript team. github.com/Microsoft/TypeScript/issues/…
is it possible it access parent context without passing this directly ?
@shabunc not to my knowledge
idea if you need a "true" (not "static") inner class (as an object template), then use it like () => new class
|
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 { }
}

1 Comment

Same. 'Foo' only refers to a type, but is being used as a namespace here.
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();

Comments

11

Static nesting

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

  1. Option 1: Implementing Bar inside namespace Foo.
    The type is automatically declared using declaration merging.

    class Foo { }
    
    namespace Foo {
        export class Bar { }
    }
    
    let bar: Foo.Bar = new Foo.Bar()
    
  2. Option 2: Implementing Bar inside class Foo.
    The type is declared in an ambient namespace declaration.

    class Foo {
        static Bar = class { }
    }
    
    declare namespace Foo {
        type Bar = typeof Foo.Bar.prototype
    }
    
    let bar: Foo.Bar = new Foo.Bar()
    

Non-static nesting

Defining the non-static nested class Foo.prototype.Bar may be done as follows.
The type is again declared in an ambient namespace declaration.

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.

Comments

4

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.