Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access members of outer class in TypeScript

Since TypeScript 1.6, we can easily create inner classes with class expressions. In other OOP-centric languages like Java, inner classes can access members of the outer class, even private ones.

This behavior is similar to concept of closures, where function could access variables from the scope in which it was defined.

Why I can't achieve this in TypeScript? Does specification of classes in ECMAScript 2015 plays role here?

Code that presents expected behavior:

class OuterClass {
    private outerField = 1337;

    public InnerClass = class { 
        public accessOuter() {
            return this.outerField; // outerField not defined
        }
    }
}

var outer = new OuterClass();
var inner = new outer.InnerClass();
var win = inner.accessOuter();
like image 299
Piotr Lewandowski Avatar asked Aug 02 '16 21:08

Piotr Lewandowski


3 Answers

It's easier to understand why you can't do that if you look at the compiled javascript of your code:

var OuterClass = (function () {
    function OuterClass() {
        this.outerField = 1337;
        this.InnerClass = (function () {
            function class_1() {
            }
            class_1.prototype.accessOuter = function () {
                return this.outerField; // outerField not defined
            };
            return class_1;
        }());
    }
    return OuterClass;
}());

As you can see, outerField is defined as a member of OuterClass like so:

this.outerField = 1337;

When you try to access it in your InnerClass you do:

return this.outerField;

But the this here is the instance of class_1 and not OuterClass so there's no outerField in this.
Also, you have no access from the inner class to the instance of the outer class.

The way this is solved in java is like so:

class OuterClass {
    private int outerField = 1337;

    public class InnerClass {
        public int accessOuter() {
            return OuterClass.this.outerField;
        }
    }
}

But there's no equivalent to OuterClass.this.outerField in typescript/javascript.
Look at typescript inner classes more like static inner classes in java, but here too you'll only be able to access public properties:

class OuterClass {
    public static outerField = 1337; // has to be public

    public InnerClass = class { 
        public accessOuter() {
            return OuterClass.outerField;
        }
    }
}

You can pass an instance of the outer class to the inner class:

class OuterClass {
    public outerField = 1337;

    public InnerClass = class {
        constructor(private parent: OuterClass) {}

        public accessOuter() {
            return this.parent.outerField;
        }
    }
}

But again, you'll need to have outerField public.


Edit

In case you want to achieve something that will simulate the needed behavior (that is, the inner class instance will have access to a private outer class members), then you can do something like this:

interface OuterClassProxy {
    outerField: number;
}

interface IInnerClass {}

class OuterClass {
    private outerField = 1337;

    static InnerClass = class implements IInnerClass {
        constructor(private parent: OuterClassProxy) {}

        public accessOuter() {
            return this.parent.outerField;
        }
    }

    public createInnerClass(): IInnerClass {
        let outerClassInstance = this;

        return new OuterClass.InnerClass({
            get outerField(): number {
                return outerClassInstance.outerField;
            },
            set outerField(value: number) {
                outerClassInstance.outerField = value;
            }
        });
    }
}

It's quite a lot of work, but it will do it.

like image 72
Nitzan Tomer Avatar answered Nov 16 '22 02:11

Nitzan Tomer


@Nitzan 's answer is great. I just wanted to add that I came up with this recently, maybe it helps:

class Outer {

    constructor() {
        this.val = 1337;
    }

    get Inner() {
        let Outer = this;
        return class {
            accessVal() { return Outer.val; }
        }
    }

}

new (new Outer()).Inner().accessVal(); // 1337
like image 20
Kosmas Papadatos Avatar answered Nov 16 '22 02:11

Kosmas Papadatos


Here is the correct way to do this in Typescript:

class OuterClass {
  private outerField = 1337;

  get InnerClass() {
    const thatOuterField = this.outerField // <-- Notice this addition
    return   class {
      public accessOuter() {
        return thatOuterField; // outerField not defined
      }
    }
  }


}

let outer = new OuterClass();
let inner = new outer.InnerClass();
let win = inner.accessOuter();

alert(win); // test works!

No need for anything convoluted.

like image 2
Somo S. Avatar answered Nov 16 '22 01:11

Somo S.