104

Is there a way to have a private setter for a property in TypeScript?

class Test
{
    private _prop: string;
    public get prop() : string
    {
        return this._prop;
    }

    private set prop(val: string)
    {
        //can put breakpoints here
        this._prop = val;
    }
}

Compiler complains that visibility for getter and setter don't match. I know I can just set the backing field, but but then I can't set breakpoints when the value is set.

I though about using an interface to hide the setter, but interfaces can only define a property, not whether it has a getter on setter.

Am I missing something here? There doesn't seem to be any reason to not allow private setters, the resulting JS doesn't enforce visibility anyway, and seems better that the current alternatives.

Am I missing something? If not is there a good reason for no private setters?

sheamus
  • 3,001
  • 4
  • 31
  • 54
  • This is a feature introduced in 4.3.5. Your above code compiles well (except for that `_prop` is not initialised). https://www.typescriptlang.org/play?ts=4.3.5#code/MYGwhgzhAEAqCmEAuBYAUAb3dH0AOATgJYBuYS80A+oQPZ4Bc0yxAdgOYDc2ueArgCMQRYNHbwk+AvQAUASmhMWRDjxxY0uLdAIS+BVtCQALIhAB0NaXm6bcAX3RqppcpQgSpssiCVI27HLOGtq4APRhwGCG-JICumAA1ni0KkgwxvC6zlomZpZ0eNAAvNA+tlqOaPZAA – Devs love ZenUML Nov 12 '22 at 09:18

3 Answers3

96

The TypeScript specification (8.4.3) says...

Accessors for the same member name must specify the same accessibility

So you have to choose a suitable alternative. Here are two options for you:

You can just not have a setter, which means only the Test class is able to set the property. You can place a breakpoint on the line this._prop =....

class Test
{
    private _prop: string;
    public get prop() : string
    {
        return this._prop;
    }

    doSomething() {
        this._prop = 'I can set it!';
    }
}

var test = new Test();

test._prop = 'I cannot!';

Probably the ideal way to ensure private access results in something akin to a "notify property changed" pattern can be implemented is to have a pair of private get/set property accessors, and a separate public get property accessor.

You still need to be cautious about someone later adding a direct call to the backing field. You could get creative in that area to try and make it less likely.

class Test
{
    private _nameBackingField: string;

    private get _name() : string
    {
        return this._nameBackingField;
    }

    private set _name(val: string)
    {
        this._nameBackingField = val;
        // other actions... notify the property has changed etc
    }

    public get name(): string {
        return this._name;
    }

    doSomething() {
        this._name += 'Additional Stuff';
    }
}
Fenton
  • 241,084
  • 71
  • 387
  • 401
  • 2
    The problem with this solution is that they don't allow += (and -=) operator: `this.prop += 'abc';` – splintor Aug 27 '17 at 20:41
  • 4
    @splintor yes but not letting others change the value is the whole point :) – Tobias J Sep 15 '17 at 13:49
  • @TobyJ Notice the `this` I used in my sample. I mean allowing `+=` and `-=` from within the class, where you can set a breakpoint. @Fenton solution suggests adding a method, but then from within the class you can't use `+=` to add to `prop`. See [my solution](https://stackoverflow.com/a/45909300/46635) for a way to do it. – splintor Sep 17 '17 at 05:46
  • @splintor in the first example you can use `this._prop += 'I can set it!';` – Fenton May 14 '18 at 07:12
  • @Fenton Of course I can, but if I want the setter to do more than just setting the `_prop` field, e.g. call `notify('_prpo', value)`, then setting `_prop` directly won't do it. – splintor May 15 '18 at 09:54
  • You say: "You can just not have a setter, which means only the Test class is able to set the property." If I understand correctly, having a getter but no setter, does not by default create a public setter as well? (This was the question I had when I found this thread.) – wearego Jul 19 '18 at 09:54
  • @wearego - that discussion is about a subversion of the type system (i.e. although these things are `private` in TypeScript, you _can_ still access them from a wider context in JavaScriptLand. It's not how anyone would recommend you write an app. – Fenton Jul 19 '18 at 12:38
  • The only justification for using a `_` as prefix is to prevent the name collition and it should only be used in the private property. The use of `_` as prefix appeared as a naming convention in programing languages where access modifiers like `private` were not a choise. – EliuX Jun 06 '23 at 16:05
  • @EliuX of course, the JavaScript that is produced is exactly this. – Fenton Jun 07 '23 at 08:29
12

I also hope we could have public getter and private setter. Until we do, another way to handle this is to add additional private getter and setter:

class Test {
  _prop: string;
  public get prop(): string {
    return this._prop;
  }

  private get internalProp(): string {
    return this.prop;
  }

  private set internalProp(value: string) {
    this._prop = value;
  }

  private addToProp(valueToAdd: string): void {
    this.internalProp += valueToAdd;
  }
}
splintor
  • 9,924
  • 6
  • 74
  • 89
5

Overview

The answers provided here are a bit outdated, although great for TypeScript version 4.2 and below. Per TypeScript's updated documentation, this is now possible as of TypeScript 4.3:

Since TypeScript 4.3, it is possible to have accessors with different types for getting and setting.

Here's a link to actual pull request and a snippet below showing this new feature.


Code

Below, the something accessors have differing visibility (public get and private set).

See this working in TypeScript Playground.

class A {
  #somethingPrivate: number = 0;

  public get something(): number {
    return this.#somethingPrivate;
  }

  private set something(newValue: number) {
    this.#somethingPrivate = Math.max(0, newValue);
  }

  public decrease(): A {
    this.something--;
    return this;
  }

  public increase(): A {
    this.something++;
    return this;
  }
}

const a = new A();
a.increase();
console.log(a.something); // 1

a.decrease().decrease().decrease();
console.log(a.something); // 0

Note: If you're wondering what the #member does, it makes it truly private at runtime. See documentation here and great answers on this SO question.

ctwheels
  • 21,901
  • 9
  • 42
  • 77