Typescript: extending an interface and redeclaring the existing fields as readonly

5.6k Views Asked by At

Let's say we have an interface like this:

interface Person {
  name: string;
  age: number;
}

I want to call Readonly and create a readonly version of the interface, e.g.

interface PersonReadonly extends Readonly<Person> {}

which will be equivalent to writing

interface PersonReadonly {
  readonly name: string;
  readonly age: number;
}

Can we write such a Readonly generic interface, or is it written already?

4

There are 4 best solutions below

0
On BEST ANSWER

You can do:

type PersonReadonly = Readonly<Person>

But it is not an interface. For example, you can't add a new member somewhere else.

Edit from May, 2017: Since TS 2.2 (February, 2017), interfaces can be derived from types.

0
On

The generic interface for making all fields of the instance readonly is available as of TypeScript 2.1.

It's called exactly Readonly<T>, so you can use it like this:

let person: Readonly<Person> = { name: 'John', age: 30 };
person.age = 31; // gives error

It's impossible to implement generic readonly type before TypeScript 2.1.

2
On

In playground the Readonly type is defined so you can do:

interface Person {
    name: string;
    age: number;
}

let a: Readonly<Person> = {
    name: "name",
    age: 3
};

a.age = 5; // Error: Cannot assign to 'age' because it is a constant or a read-only property

(code in playground)

If the type is defined in your environment then you can simply add it:

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

But it requires you to have typescript 2.1 and above.
If you don't have it, it probably means that your typescript version is below 2.1, otherwise you can just use it.

1
On

To answer you explicit question: You technically can't do what you want yet.

Type Mapping will create a Type not an interface -- Readonly<T> is a built in type mapper that will return a Type. You cannot implement or extend Type aliases, only interface / class.

thus:

interface PersonReadonly extends Readonly<Person> {}

is invalid until support for implementing types are done, if ever.

That doesn't stop you passing around the Type though; and you can use union on types to build up more complex types too. thus you can do:

type PersonWithState = Readonly<Person> & { state: string }

let person = <Person>{ name: "John", age: 20 };
let personState = <PersonWithState>{ ...person, state: "online" };

personState.age = "30"; // error;
personState.state = "offline"; // OK.

but you cannot have a class implement, or interface extend, PersonWithState - yet.