user1769949
user1769949

Reputation: 3

Invoking Typescript public setter modifies private property without running logic contained in the public setter

I have a class which contains a private property which is an array. There is a public getter and setter to ensure that data manipulation triggers required business logic.

I ran into a problem, when a caller was able to set the private property without running the associated logic contained in the public setter.

I reproduced the issue with this simple code:

class Greeter {
    private _greetings: string[];
    constructor () {}
    set greetings(greetings: string[]) {
        let newGreetings: string[] = [];
        for (let i: number = 0; i < greetings.length; i++) {
            newGreetings.push("setter_" + greetings[i] )
        }
        this._greetings = newGreetings;
    }

    get greetings() {
        return this._greetings;
    }
}

let greeter = new Greeter();
greeter.greetings = ["test"];
let test1 = document.createElement('p');
test1.textContent = greeter.greetings[0];
document.body.appendChild(test1);

greeter.greetings[0] = "test";
let test2 = document.createElement('p');
test2.textContent = greeter.greetings[0];
document.body.appendChild(test2);

To view the code in TS playground, follow the below link:

https://www.typescriptlang.org/play/#src=class%20Greeter%20%7B%0D%0A%20%20%20%20private%20_greetings%3A%20string%5B%5D%3B%0D%0A%20%20%20%20constructor%20()%20%7B%7D%0D%0A%20%20%20%20set%20greetings(greetings%3A%20string%5B%5D)%20%7B%0D%0A%09%09let%20newGreetings%3A%20string%5B%5D%20%3D%20%5B%5D%3B%0D%0A%09%09for%20(let%20i%3A%20number%20%3D%200%3B%20i%20%3C%20greetings.length%3B%20i%2B%2B)%20%7B%0D%0A%09%09%09newGreetings.push(%22setter_%22%20%2B%20greetings%5Bi%5D%20)%0D%0A%09%09%7D%0D%0A%09%09this._greetings%20%3D%20newGreetings%3B%0D%0A%09%7D%0D%0A%09%0D%0A%09get%20greetings()%20%7B%0D%0A%09%09return%20this._greetings%3B%0D%0A%09%7D%0D%0A%0D%0A%7D%0D%0A%0D%0Alet%20greeter%20%3D%20new%20Greeter()%3B%0D%0Agreeter.greetings%20%3D%20%5B%22test%22%5D%3B%0D%0Alet%20test1%20%3D%20document.createElement('p')%3B%0D%0Atest1.textContent%20%3D%20greeter.greetings%5B0%5D%3B%0D%0Adocument.body.appendChild(test1)%3B%0D%0A%0D%0Agreeter.greetings%5B0%5D%20%3D%20%22test%22%3B%0D%0Alet%20test2%20%3D%20document.createElement('p')%3B%0D%0Atest2.textContent%20%3D%20greeter.greetings%5B0%5D%3B%0D%0Adocument.body.appendChild(test2)%3B%0D%0A

My question is: Am I doing something wrong? Is this the intended behavior? Or is this more of an issue with typescript compiler which should not allow this code to compile?

Upvotes: 0

Views: 29

Answers (1)

Ryan Cavanaugh
Ryan Cavanaugh

Reputation: 220884

Looking specifically at these two lines:

greeter.greetings = ["test"];

and

greeter.greetings[0] = "test"

They look similar, but they're really quite different. This line here says set the greetings property of greeter to ["test"]. The greetings setter is invoked because we're setting the greetings property.

greeter.greetings = ["test"];

In contrast, this line should really be thought of as two operations

// this line...
greeter.greetings[0] = "test"
// ...is the same as
const tmp = greeter.greetings;
tmp[0] = "test";

As you can see, we don't set the greetings property, we get it! Then we set a property of the array. The greetings setter is not used.

Starting in TypeScript 2.0 (in beta at time of writing) you'll be able to use ReadonlyArray<string> to represent arrays that can be read but not written to. This will let you expose a ReadonlyArray<string> to consumers but have a regular string[] as a private field.

Upvotes: 1

Related Questions