Introduction to TypeScript Interfaces
Tags: typescript, javascript, beginners, tutorial
Published: 2020-01-05 21:56:10

The big advantage of TypeScript over plain JavaScript is that it extends the features of JavaScript by adding type safety to our program’s objects. It does this by checking the shape of the values that objects can take on. Checking the shape is called duck typing or structural typing. It’s very useful for defining contracts within our code in TypeScript programs. In this article, we’ll look at how to define a TypeScript interface and add required or optional properties to it.

Defining Interfaces
===================

To define a basic interface, we use the `interface` keyword in TypeScript. This keyword is exclusive to TypeScript and it’s not available in JavaScript. We can define a TypeScript interface like in the code below:

```typescript
interface Person {  
  name: string  
}
```

In the code above, if a variable or parameter has been designated with this interface, then all objects with the type using it will have the `name` property. Object literals assigned to a variable with the type cannot have any other property. Arguments that are passed in as parameters that are of this type also can only have this property. For example, the following code will be compiled successfully and run with the TypeScript compiler:

```typescript
interface Person{  
  name: string  
}

const greet = (person: Person) => {  
  console.log(`Hello, ${person.name}`);  
}  
greet({ name: 'Joe' });
```

The code above will compile and run since it only has the `name` property and the value of it is a string exactly like it’s specified in the `Person` interface. However, the following code wouldn’t be compiled by the TypeScript compiler and throw an error:

```typescript
interface Person {  
  name: string  
}

const greet = (person: Person) => {  
  console.log(`Hello, ${person.name}`);  
}
greet({ name: 'Joe', foo: 'abc' });
```

This is because we specified that the type of the `person` parameter in the `greet` function has the type `Person`. The object passed in as the argument can only have the `name` property and that the value of it can only be a string. We can’t assign an object literal with different properties than what’s listed in the interface if a variable has been designated by the type of the interface:

```typescript
const p: Person = { name: 'Joe', foo: 'abc' };
```

The code above also wouldn’t compile since the object literal has an extra property in it that is not listed in the `Person` interface. In editors that support TypeScript like Visual Studio Code, we would get the error message “Type ‘{ name: string; foo: string; }’ is not assignable to type ‘Person’. Object literal may only specify known properties, and ‘foo’ does not exist in type ‘Person’.”

With interfaces, when designating the types of variables, we get auto-completion of our code when we write object literals.

Optional Properties
===================

TypeScript interfaces can have optional properties. This makes interfaces much more flexible than just adding required properties to them. We can designate a property as optional with the question mark `?` after the property name. For example, we can write the following code to define an interface with an optional property:

```typescript
interface Person {  
  name: string,  
  age?: number  
}
```

In the code above, `age` is an optional property since we put a question mark after the property name. We can use it as the following code:

```typescript
interface Person {  
  name: string,  
  age?: number  
}

const greet = (person: Person) => {  
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);  
}

greet({ name: 'Joe', age: 10 });
```

In the object we passed into the `greet` function into the code above, we passed in the `age` property and a number for its value. Then we used it inside our code by checking if it’s defined first and then we add additional text to the main string if it is. We can also omit optional properties like in the following code:

```typescript
interface Person {  
  name: string,  
  age?: number  
}

const greet = (person: Person) => {  
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);  
}

greet({ name: 'Joe' });
```

The code above would still run since the `age` property has been designated as being optional. The rules outlined by the interface still apply if we assign an object literal to it. For example, if we write:

```typescript
interface Person {  
  name: string,  
  age?: number  
}

const greet = (person: Person) => {  
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);  
}

const person: Person = { name: 'Joe' };  
greet(person);
```

This would still work since we stick to the property descriptions defined in the `Person` interface. If we add the `age` property with a number property it would still compile and run:

```typescript
interface Person {  
  name: string,  
  age?: number  
}

const greet = (person: Person) => {  
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);  
}

const person: Person = { name: 'Joe', age: 20 }  
greet(person);
```

Optional properties are useful in that we can define properties that can possibly be used while preventing the use of properties that aren’t part of the interface. This prevents bugs that arise because of typos in our code.


Readonly Properties
===================

To make properties that are non-modifiable after the object is first created, we can use the `readonly` keyword in front of a property to designate that the property can only be written once when the object is being created and not any time after. For example, we can write the following code:

```typescript
interface Person {  
  readonly name: string,  
  age?: number  
}

const greet = (person: Person) => {  
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);  
}

const person: Person = { name: 'Joe', age: 20 };  
greet(person);
```

We added the `readonly` keyword in front of the `name` property so that it can only be changed once and only once. So if we try to assign something new to the `name` property, the TypeScript compiler wouldn’t compile and code and the code wouldn’t run:

```typescript
interface Person{  
  readonly name: string,  
  age?: number  
}

const greet = (person: Person) => {  
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);  
}

let person: Person = { name: 'Joe', age: 20 };  
person.name = 'Jane';  
greet(person);
```

The code above will get us the error message “ Cannot assign to ‘name’ because it is a read-only property” when we try to compile it or in text editors that support TypeScript. However, we can reassign the whole object to a different value like in the following code:

```typescript
interface Person {  
  readonly name: string,  
  age?: number  
}

const greet = (person: Person) => {  
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);  
}

let person: Person = { name: 'Joe', age: 20 };  
person = { name: 'Jane', age: 20 };  
greet(person);
```

Then instead of logging ‘Hello, Joe. You’re 20 years old.’, we get ‘Hello, Jane. You’re 20 years old.’

If we want to designate an array as read-only, that is, only be changed when it’s created, we can use the `ReadonlyArray` type that’s the same as `Array`, but all the mutating methods are removed from it so that we can’t accidentally change anything in the array. For example, we can use it as in the following code:

```typescript
let readOnlyArray: ReadonlyArray<number> = [1,2,3];
```

Then if we try to write the following code, the TypeScript compiler would give errors:

```typescript
readOnlyArray[0] = 12;  
readOnlyArray.push(5);  
readOnlyArray.length = 100;
```

Then we get the following errors:

```typescript
Index signature in type 'readonly number[]' only permits reading.(2542)Property 'push' does not exist on type 'readonly number[]'.(2339)Cannot assign to 'length' because it is a read-only property.(2540)
```

If we want to convert a `ReadonlyArray` back to a writable array, we can use the type assertion `as` operator to convert it back to a regular array:

```typescript
let arr = readOnlyArray as number[];
```

Conclusion
----------

TypeScript interfaces are very handy for defining contracts within our code. We can use it to designate types of variables and function parameters. They let us know what properties a variable can take on, and whether they’re required, optional, or read-only. We can define interfaces with the `interfaces` keyword, optional properties with the question mark after a variable name, and the `readonly` keyword for read-only properties.