basarat
basarat

Reputation: 276289

Can you create nested classes in TypeScript?

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

Upvotes: 168

Views: 138124

Answers (6)

Warnio
Warnio

Reputation: 171

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.

Upvotes: 11

basarat
basarat

Reputation: 276289

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

Upvotes: 234

Mohab
Mohab

Reputation: 41

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

Upvotes: 3

bnieland
bnieland

Reputation: 6496

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

Upvotes: 69

Dan Def
Dan Def

Reputation: 1931

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 { }
}

Upvotes: 23

danvk
danvk

Reputation: 16903

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

Upvotes: 17

Related Questions