user5688956
user5688956

Reputation:

(Typescript) Setter is not called on a sub-property of a class instance

this is a difficult question that I know is a problem for lots of programs (I will elaborate on this at the end). I want to create a custom setter in typescript but the datatype of the property being set is not just a number, string, bool it is actually a class object. This works fine - but if a property of the class instance is modified then the setter is not called. Here is an example of such a situation:

//This class contains two properties
class Point
{
    public x : number;
    public y : number;

    constructor(x : number, y : number) { this.x = x; this.y = 0; }
}

//How here is another class that contains a Point
//But it is private and a getter/setter is used
class PointStore
{
    private _foo : Point;

    public get foo() : Point { return this._foo; }

    //Here is the problem, the setter is only called when the whole of foo is changed
    public set foo(n : Point) { this._foo = n; console.log("Foo has been set!"); }

    constructor() { this._foo = new Point(0, 0); }
}

//Use case
let bar : PointStore = new PointStore();

bar.foo = new Point(10, 10); //Logs "Foo has been set!"
bar.foo.x = 20; //Doesn't log anything

The problem is pretty clear from the example but I just want to say the following:

Is there anyway around this at all? Because I have seen from APIs such as Unity3D they have opted to make their 'Point' class have only private members and so data can only be set through the constructor e.g:

//the 'Unity' solution
transform.position = new Vector2(10, 10); //Okay
transform.position.x = 20; //Error

But this is not at all a perfect solution to the problem, as it makes programming with the 'Point' class much more difficult from then on.

If anyone has a trick to solve this, it would be greatly appreciated.

Upvotes: 1

Views: 3083

Answers (3)

Trash Can
Trash Can

Reputation: 6814

The great thing about typescript is that, 2 types are compatible if they have the same shape. So, based on what you described, I think this seems to fit the bill and this will not break your code because PointStore here is compatible with Point class

class PointStore
{
    private x : number; 
    private y : number;

    constructor(x: number,  y: number) {
        this.x = x;
        this.y = y;
    };

    public get x() { return this.point.x; };

    public set x(x: number ) { 
        // your custom logic here
        this.point.x = x; 
    };

     // setter and getter for other y omitted

}

Upvotes: 0

Nitzan Tomer
Nitzan Tomer

Reputation: 164129

You can use a Proxy for that:

class PointStore {
    private _foo: Point;

    constructor() {
        this.createProxy(new Point(0, 0));
    }

    public get foo(): Point { return this._foo; }

    public set foo(point: Point) {
        this.createProxy(point);
        console.log("Foo has been set!");
    }

    private createProxy(point: Point) {
        this._foo = new Proxy(point, {
            set: function (target: Point, property: string, value: any) {
                target[property] = value;
                console.log("Foo has been set (using proxy)!");
                return true;
            }
        });
    }
}

(code in playground)

Upvotes: 1

Shai
Shai

Reputation: 3872

The setter will be used only when you assign a value to the property. One way you can circumvent this is by using Object.assign, like so:

bar.foo = new Point(10, 10);
bar.foo = Object.assign(bar.foo, {x: 20})

You can also go deeper:

bar.foo = Object.assign(bar.foo, {x: {z: 20} })

Upvotes: 1

Related Questions