103

I would like to define an interface with a readonly property. For instance (as a strawman);

interface foo {
    get bar():bool;
}

However, this gives the syntax error, "expected ';'" on bar. I have setup my VisualStudio to use the ES5 target, so getters are supported. How would I define a readonly property in an interface?

Ezward
  • 17,327
  • 6
  • 24
  • 32
  • 2
    See http://stackoverflow.com/questions/12838248/is-it-possible-to-use-getters-setters-in-interface-definition – Hans Passant Oct 11 '12 at 17:47
  • 2
    Yes, this is a limitation of interfaces. See also [this question][1]. [1]: http://stackoverflow.com/questions/12838248/is-it-possible-to-use-getters-setters-in-interface-definition – Valentin Oct 11 '12 at 18:14
  • 2
    Does this answer your question? [Is it possible to use getters/setters in interface definition?](https://stackoverflow.com/questions/12838248/is-it-possible-to-use-getters-setters-in-interface-definition) – Michael Freidgeim May 14 '20 at 14:18
  • I'm happy with the accepted answer below. The link you provide also has good answers. This accepted answer has a lot of information on how readonly, optional and getters interact https://stackoverflow.com/questions/12827266/get-and-set-in-typescript/12850536 – Ezward Jul 14 '21 at 17:45
  • This has been marked as a duplicate, but it is not. The question is about readonly properties in interfaces; the duplicate is specifically about getters/setters in interfaces. The question uses a getter to implement readonly as a strawman. However, the accepted answer is much better; readonly as an attribute. Look at the accepted answers on the two questions and it you can see it is not a duplicate. – Ezward May 23 '22 at 17:47

3 Answers3

130

Getter-only properties were introduced in Typescript 2.0:

interface foo {
    readonly bar: boolean;
}
Vitaliy Ulantikov
  • 10,157
  • 3
  • 61
  • 54
  • 8
    If I'm not mistaken, this still declares `bar` as a property, not a getter. – Alexander Abakumov Sep 21 '17 at 19:24
  • 15
    @AlexanderAbakumov the readonly does not specify that it has to be a property. As properties are referenced in the same ways as getters, the class implementing this interface is free to use a property or a getter. – nikeee Nov 05 '17 at 20:59
  • 3
    @nikeee: Yes, but OP asked if we can use getters/setters in interfaces, not properties. – Alexander Abakumov Nov 06 '17 at 00:12
  • 4
    @AlexanderAbakumov but that's exactly what this is, a getter. If you try to assign to the property you get this error `Error TS2540 (TS) Cannot assign to 'bar' because it is a constant or a read-only property.` – Simon_Weaver May 12 '18 at 04:33
  • 1
    Note that this feature only works for *object literals* that implement the interface. (See the `Point` example in the linked article). However, if you try to write a *class* that implements the interface, the `readonly` modifier doesn't apply. – chharvey Sep 14 '18 at 04:41
  • 7
    That's a readonly property not a getter. There's a slight difference as shown here: property: readonly bar: boolean getter: get bar(): boolean { return a && b || c && !e || (x | y | z) } – Rick O'Shea Oct 14 '18 at 19:55
  • You can then write a class like this class Bar implements foo { get bar (): boolean { return true } } – Jan Sverre Jan 12 '19 at 12:12
  • Does that imply that the class must implement it as readonly, in that case it's not the same contract as stating that a getter is present. – Henrik Vendelbo Jan 28 '19 at 11:35
  • 1
    this is a _partial answer_ but it is not strictly correct. the implementation is free to provide the property either as a typical property or as a computed property (getter). that freedom is valuable in some instances, but insufficiently specific in others. Is there a way to specify that they value must be computed? – roberto tomás Sep 05 '19 at 15:33
  • 3
    @robertotomás This is not only a partial answer, but the complete, correct answer. The whole reason for having interfaces is to specify contracts that doesn't care about implementation details. If you want to specify implementation details, you use super classes, not interfaces. – DennisK Sep 12 '19 at 09:07
  • 1
    None of that is in the answer, so no it is not complete – roberto tomás Sep 12 '19 at 10:48
  • @DennisK, it isn't simply an implementation detail because behavior during a spread operator is different. In the example above `const x = {...y}` when `y` is a `interface foo` will believe that the type of `x` is `{bar: boolean}`. But if `y` is actually `class foo { get bar(): boolean }` then it knows that x is `{}`. There doesn't seem to be a way to tell typescript that you expect a getter unless you construct a whole implementation of it. If I specify an interface with readonly property and someone gives me a class with a getter, (or vice versa) it breaks but typescript doesn't warn – Warren Apr 24 '21 at 20:28
  • @Warren That seems more like a problem with TypeScript's type inference. When it breaks, be specific. In this case, use `y:foo = implementation`. – DennisK Apr 26 '21 at 07:01
  • @Warren Whoops, you reused the foo name. I'll probably need a larger example to be precise: ```typescript interface foo { readonly bar: boolean; } class baz implements foo { private _bar = false get bar(): boolean { return this._bar; }; } const test = () => { const y1: foo = { bar: true }; const y2:foo = new baz(); const x1 = {...y1}; const x2 = {...y2}; console.log(x1.bar); console.log(x2.bar); }; ``` Edit: Argh. Comments on StackOverflow are pretty annoying. Sorry about the formatting. – DennisK Apr 26 '21 at 07:08
  • @Warren Wow, just tried actually running this, and you're right (and I misread your comment). The spread operator only copies concrete properties. My bad. – DennisK Apr 26 '21 at 07:57
  • I agree that a readonly property is an interface is actually better than a getter in an interface, because you can implement it as you wish. – Ezward Jul 14 '21 at 17:40
22

Yes, this is a limitation of interfaces. Whether or not the access to the property is implemented with a getter is an implementation detail and thus should not be part of the public interface. See also this question.

If you need a readonly attribute specified in an interface, you can add a getter method:

interface foo {
    getAttribute() : string;
}
Community
  • 1
  • 1
Valentin
  • 7,874
  • 5
  • 33
  • 38
  • 12
    Unfortunately, it's not an implementation detail that is is read-only. I wish I could express that in Typescript. – Ezward Oct 12 '12 at 00:04
  • I see. Then I think your only way is to specify a getter method. I have updated my answer accordingly. – Valentin Oct 12 '12 at 05:56
  • 2
    Presumably this will become possible in TypeScript 2.0: https://github.com/Microsoft/TypeScript/pull/6532 – Michael Younkin Mar 21 '16 at 02:31
6

As @Vitaliy Ulantikov answered, you may use the readonly modifier on a property. This acts exactly like a getter.

interface Point {
    readonly x: number;
    readonly y: number;
}

When an object literal implements the interface, you cannot overwrite a readonly property:

let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!

But when a class implements the interface, there is no way to avoid overwriting it.

class PointClassBroken implements Point {
    // these are required in order to implement correctly
    x: number;
    y: number;

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

    changeCoordinates(x: number, y: number): void {
        this.x = x // no error!
        this.y = y // no error!
    }
}

I guess that’s because when you re-declare properties in the class definition, they override the properties of the interface, and are no longer readonly.

To fix that, use readonly on the properties directly in the class that implements the interface

class PointClassFixed implements Point {
    readonly x: number;
    readonly y: number;

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

    changeCoordinates(x: number, y: number): void {
        this.x = x // error!
        this.y = y // error!
    }
}

See for yourself in the playground.

chharvey
  • 8,580
  • 9
  • 56
  • 95