Michael Lorton
Michael Lorton

Reputation: 44376

Why is a private property part of the interface?

Consider the following:

class X  {
    bar() {
        return "bar"
    }
    constructor(private readonly x: number){}
}

interface Y extends X {
}


const f = (y: Y) => { console.log(y.bar()); }

f({ bar: () => "tavern"});

It does not compile, because x is missing.

f({ bar: () => "tavern", x: 1});

does not compile because x isn't private.

Re-writing the code so it's possible to declare the x private

class Y implements X {
    bar() {
        return "tavern"
    }
    private x = 1;
}

is rejected because "types have separate declarations".

The only solution I have found is to remove the private from the constructor.

What I'd really like to do is the first: I don't care about private properties of the class, and I especially don't care about private members declared within the constructor.

My two questions are:

  1. Why is this the case?
  2. What, if anything, can I do so I don't have to worry about private properties?

Upvotes: 2

Views: 324

Answers (1)

Michael Lorton
Michael Lorton

Reputation: 44376

Ryan Cavanaugh, development lead for the Typescript team at Microsoft, wrote:

Allowing the private fields to be missing would be an enormous problem, not some trivial soundness issue.

Consider this code:

class Identity {
  private id: string = "secret agent";
  public sameAs(other: Identity) {
    return this.id.toLowerCase() === other.id.toLowerCase();
  }
}

class MockIdentity implements Identity {
  public sameAs(other: Identity) { return false; }
}

MockIdentity is a public-compatible version of Identity but attempting to use it as one will crash in sameAs when a non-mocked copy interacts with a mocked copy.

Which sucks. It makes perfect sense, but it sucks.

But I found a workaround that solves my problem:

type Public<T> = {
    [P in keyof T]: T[P];
};

class X  {
    bar() {
        return "bar"
    }
    constructor(private readonly x: number){}
}

interface Y extends Public<X> { }

const f = (y: Y) => { console.log(y.bar()); }

f({ bar: () => "tavern"});

This allows me to mock out complex types reliably, without having to also mock out private data.

This is safe in a test context, where mocked instances and real instances never interact.

Upvotes: 4

Related Questions