TypeScript mapped types. Part 2

This blog is a part of my TypeScript series, and the previous ones are:

1. Why program in TypeScript
2. Structural vs nominal typing
3. Getting started with TypeScript classes
4. Access modifiers public, private, and protected
5. Abstract classes
6. enums
7. An intro to TypeScript generics
8. TypeScript mapped types. Part 1

In the previous blog, I showed you how the transformation function for the built-in mapped type Readonly was declared in the file typescript/lib/lib.es5.d.ts:

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

You can define your own transformation functions using similar syntax. Let’s try to define the type Modifiable – an opposite to Readonly.

We took a type Person made all of its properties read-only by applying Readonly mapped type: Readonly. Let’s consider another scenario. Say, the properties of the type Person were originally declared with the readonly modifier as follows:

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

How can you remove the readonly qualifiers from the Person declaration if need be? There is no built-in mapped type for it, so let’s declare one:

type Modifiable = {
  -readonly[P in keyof T]: T[P];
};

The minus sign in front of the readonly qualifier removes it from all properties of the given type. Now you can remove the readonly restriction from all properties by applying the mapped type Modifiable:

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

const worker1: Person = { name: "John", age: 25};

worker1.age = 27;  // compile error

const worker2: Modifiable = { name: "John", age: 25};

worker2.age = 27;  // No errors here 

You can see this code in the Playground at https://bit.ly/2GMAf3c.

Other built-in mapped types

You know that if a property name in the type declaration ends with the modifier ?, this property is optional. Say we have the following declaraion of the type Person:

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

Since none of the Person’s properties names ends with a question mark, providing values for name and age is mandatory. What if you have a need in type that has the same properties as in Person, but all of its properties should be optional? This is what the mapped type Partial is for. Its mapping function is declared in lib.es5.d.ts as follows:

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

Have you spotted the question mark there? Basically, we create a new type by appending the question mark to each property name of the given type. The mapped type Partial makes all properties in the given type optional. The following screenshot was taken while I was hovering the mouse over the declaration of the worker1 variable.

It shows an error message because the variable worker1 has the type Person, where each property is required, but the value for age was not provided. There are no errors in initializing worker2 with the same object because the type of this variable is Partial, so all its properties are optional.

There is a way to make all properties of a type optional, but can you do the opposite? Can you take a type that was declared with some optional properties and make all of them required? You bet! This can be done with the mapped type Required that’s declared as follows:

type Required<T> = {
    [P in keyof T]-?: T[P];  // The -? means remove the modifier ?.
};

The next screenshot was taken while I was hovering the mouse over the declaration of the worker2 variable. The properties age and name were optional in the base type Person but are required in the mapped type Required hence the error about missing age.

Tip: The type Required was introduced in TypeScript 2.8. If your IDE doesn’t recognize this type, make sure it uses the proper version of the TypeScript language service. In Visual Studio Code you can see its version in the bottom right corner. Click on it to change to a newer version if you have it installed.

You can apply more than one mapped type to a given type. In the next listing, I apply Readonly and Partial to the type Person. The former will make each property read-only and the latter will make each property optional. There I initialize the property name but not the optional age. The property name is read-only and can be initialized only once.

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

const worker1: Readonly<Partial<Person>>   
                       = { name: "John" }; 

worker1.name = "Mary"; // compiler's error 

TypeScript offers yet another useful mapped type called Pick. It allows you to declare a new type by picking a subset of properties of the given type. Its transformation function looks like this:

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

The first argument expects an arbitrary type T, and the second – a subset K of the properties of this T. You can read it as “From T, pick a set of properties whose keys are in the union K”. The next listing shows the type Person that has three properties. With the help of Pick, we declare a mapped type PersonNameAddress that has two string properties: name and address as seen in the following listing.

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

type PersonNameAddress<T, K> = Pick<Person, 'name' | 'address' >; 

The moral of the mapped types story? Mapped types allow you to create apps that have a limited number of basic types and many derived types based on the basic ones.

Advertisements

TypeScript mapped types. Part 1.

This blog is a part of my TypeScript series, and the previous ones are:

1. Why program in TypeScript
2. Structural vs nominal typing
3. Getting started with TypeScript classes
4. Access modifiers public, private, and protected
5. Abstract classes
6. enums
7. An intro to TypeScript generics

NOTE: Understanding of the generics syntax is a pre-requisite for understanding this blog.

Mapped types allow you to create new types from the existing ones. This is done by applying a transformation function to an existing type. In this blog, you’ll see how they work by looking at the type Readonly that comes with TypeScript. In the next blog, I’ll show you how to create your own mapped types.

The mapped type Readonly

Imagine that you need to pass the objects of type Person (shown next) to the function doStuff() for processing.

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

This class is used in multiple places, but you realized that you don’t want to allow the function doStuff() to accidentally modify some of the Person’s properties like age.

const worker: Person = { name: "John", age: 22};

function doStuff(person: Person) {

    person.age = 25; // We don’t want to allow this
}

None of the properties of the type Person was declared with the readonly modifier. Should we declare another type just to be used with doStuff() as follows?

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

Does it mean that you need to declare (and maintain) a new type each time when you need to have a read-only version of the existing one? There is a better solution. We can use a built-in mapped type Readonly to turn all the properties of a previously declared type to be readonly. We’ll just need to change the signature of the function doStuff() to take the argument of type Readonly instead of Person, just like this:

const worker: Person = { name: "John", age: 22};

function doStuff(person: Readonly) { 

    person.age = 25;   // This line generates a compiler error
}

To understand why an attempt to change the value of the property age generates a compiler error, you need to see how the type Readonly is declared, which in turn requires an understanding of the type keyof.

The type keyof

Reading the declarations of the built-in mapped types in the file typescript/lib/lib.es5.d.ts (it comes with TypeScript installation) helps in understanding their inner-workings and requires familiarity with the TypeScript’s index type query keyof.

You can find the following declaration of the Readonly mapping function in lib.es5.d.ts:

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

We assume that you’ve read about generics in chapter 4, and you know what in angle rackets means. Usually, the letter T in generics represents type, K – key, V – value, P – property et al.

keyof is called index type query and it represents a union of allowed property names (the keys) of the given type. If the type Person would be our T, then keyof T would represent the union of name and age. The next screenshot was taken while hovering the mouse over the custom type propNames. As you see, the type of propName is a union of name and age.


In the previous listing, the fragment [P in keyof T] means “give me the union of all the properties of the given type”. This seems as if we’re accessing the elements of some object, but actually, this is done for declaring types. The keyof type query can be used only in type declarations.

Now we know how to get access to the property names of a given type, but to create a mapped type from the existing one, we also need to know the property types. In case of the type Person, we need to be able to find out programmatically that the property types are string and number.

This is what lookup types are for. The piece T[P] is a lookup type, and it means “Give me the type of a property P”. The next screenshot was taken while hovering the mouse over the type propTypes. The types of properties are string and number.

Now let’s read the code in the previous listing  one more time. The declaration of the type Readonly means “Find the names and types of the properties of the provided concrete type and apply the readonly qualifier to each property”.

In our example, Readonly will create a mapped type that will look like this:

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

Now you see why an attempt to modify the person’s age results in the compiler’s error “Cannot assign to age because it’s a read-only property”. Basically, we took an existing type Person and mapped it to a similar type but with the read-only properties. You can try this code in the Playground.

You may say, “OK, I understand how to apply the mapped type Readonly, but what’s the practical use of it?” Those of you who purchased our book “TypeScript Quickly”  will see plenty of examples of using Readonly while going through the code of a sample blockchain app that comes with the book. For example, in chapter 10 in listing 10.15 you can see two methods that use the Readonly type with their message argument:

replyTo(client: WebSocket, message: Readonly): void

This method can send messages to blockchain nodes over the WebSocket protocol. The messaging server doesn’t know what types of messages are going to be sent, and the message type is generic. To prevent accidental modification of the message inside replyTo(), we use the mapped type Readonly there.

In the next blog, I’ll show you how to create your own mapped types.

An intro to TypeScript generics

This blog is a part of my TypeScript series, and the previous ones are:

1. Why program in TypeScript
2. Structural vs nominal typing
3. Getting started with TypeScript classes
4. Access modifiers public, private, and protected
5. Abstract classes
6. enums

We know that TypeScript has built-in types and you can create the custom ones as well. But there’s more to it. Strange as it may sound, types can be parameterized, i.e. you can provide a type (not the value) as a parameter.

It’s easy to declare a function that takes parameters of specific concrete types e.g. a number and a string:

function calctTax(income: number, state: string){...}

But TypeScript generics allow you to write a function that can work with a variety of types. In other words, you can declare a function that works with a generic type(s), and the concrete type(s) can be specified later by the caller of this function.

In TypeScript, you can write generic functions, classes, or interfaces. A generic type can be represented by an arbitrary letter(s), e.g. T in Array<T>, and when you declare a specific array, you provide a concrete type in angle brackets, e.g. number:

let lotteryNumbers: Array<number>;

In this blog, you’ll learn how to use generic code written by someone else as well as how to create your own classes, interfaces, and functions that can work with generic types.

Understanding Generics

A generic is a piece of code that can handle values of multiple types that are specified at the moment of using this piece of code (e.g. function invocation or class instantiation). Let’s consider TypeScript arrays, which can be declared as follows:

  1. Specify the type of the array element followed by []:

    const someValues: number[];

  2. Use a generic Array followed by the type parameter in angle brackets:

    const someValues: Array<number>;

With the second syntax, the angle brackets represent a type parameter. You can instantiate this the Array like any other while restricting the type of allowed values, which is number in our example.

The next code snippet creates an array that will initially have 10 objects of type Person, and the inferred type of the variable people is Person[].

class Person{ }

const people = new Array<Person>(10);

TypeScript arrays can hold objects of any type, but if you decide to use the generic type Array, you must specify which particular value types are allowed in the array, e.g. Array<Person>. By doing this, you place a constraint on this instance of the array. If you were to try to add an object of a different type to this array, the TypeScript compiler would generate an error. In another piece of code, you can use an array with a different type parameter, e.g. Array<Customer>.

The code in the next listing declares a class Person, its descendant Employee, and a class Animal. Then it instantiates each class and tries to store all these objects in the workers array using the generic array notation with the type parameter Array<Person>.

class Person {  
    name: string;
}

class Employee extends Person {  
    department: number;
}

class Animal {  
    breed: string;
}

const workers: Array<Person> = []; 

workers[0] = new Person();  
workers[1] = new Employee(); 
workers[2] = new Animal(); // compile-time error

The last line won’t compile because the array workers was declared with a type parameter Person, and our Animal is not a Person. But the class Employee extends Person and is considered a subtype of a Person; you can use the subtype Employee anywhere where the supertype Person is allowed

So, by using a generic array workers with the parameter <Person>, we announce our plans to store only instances of the class Person or its subtypes there. An attempt to store an instance of the class Animal (as it was defined in the previous listing ) in the same array will result in the following compile-time error “Type Animal is not assignable to type Person. Property name is missing in type Animal.” In other words, using TypeScript generics helps you to avoid errors related to using the wrong types.

Note

The term generic variance is about the rules for using subtypes and supertypes in any particular place of your program. For example, in Java, arrays are covariant, which means that you can use Employee[] (the subtype) where the array Person[] (the supertype) is allowed.
Since TypeScript supports structural typing, you can use either an Employee or any other object literal that’s compatible with the type Person where the Person type is allowed. In other words, generic variance applies to objects that are structurally the same. Given the importance of anonymous types in JavaScript, an understanding of this is important for the optimal use of generics in Typescript.
To see if type A can be used where type B is expected, read about structural subtyping.

Tip

We used const (and not let) to declare the identifier workers because its value never changes in the above listing. Adding new objects to the array workers doesn’t change the address of the array in memory hence the value of the identifier workers remains the same.

If you’re familiar with generics in Java or C#, you may get a feeling that you understand TypeScript generics as well. There is a caveat, though. While Java and C# use the nominal type system, TypeScript uses the structural one as explained in this blog.

In the nominal system, types are checked against their names, but in a structural system, by their structure. In languages with the nominal type system, the following line would always result in an error:

let person: Person = new Animal();

With a structural type system, as long as the structures of the type are similar, you may get away with assigning an object of one type to a variable of another. Let’s add the property name to the class Animal, as seen below.

class Person {
    name: string;
}

class Employee extends Person {
    department: number;
}

class Animal {
    name: string; 
    breed: string;
}

const workers: Array<Person> = [];

workers[0] = new Person();
workers[1] = new Employee();
workers[2] = new Animal();  // no errors

Now the TypeScript compiler doesn’t complain about assigning an Animal object to the variable of type Person. The variable of type Person expects an object that has a property name, and the Animal object has it! This is not to say that Person and Animal represent the same types, but these types are compatible.

Moreover, you don’t even have to create a new instance of PersonEmployee, or Animal classes but use the syntax of object literals instead. Adding the following line to the above listing is perfectly fine because the structure of the object literal is compatible with the structure of type Person:

workers[3] = { name: "Mary" };

On the other hand, trying to assign the Person object to a variable of type Animal will result in the compilation error:

const worker: Animal = new Person(); // compilation error

The error message would read “Property breed is missing in type Person”, and it makes sense because if you declare a variable worker of type Animal but create an instance of the object Person that has no property breed, you wouldn’t be able to write worker.breed hence the compile-time error.

Note

The previous sentence may irritate savvy JavaScript developers who’re accustomed to adding object properties like worker.breed without thinking twice. If the property breed doesn’t exist on the object worker, the JavaScript engine would simply create it, right? This works in the dynamically typed code, but if you decided to use the benefits of the static typing, you have to play by the rules. When in Rome, do as the Romans do.

Generics can be used in a variety of scenarios. For example, you can create a function that takes values of various types, but during its invocation, you must explicitly specify a concrete type. To be able to use generic types with a class, interface, or a function, the creator of this class, interface, or function has to write them in a special way to support generic types.

 Open TypeScript’s type definition file (lib.d.ts) from the TypeScript GitHub repository at line 1008 and you’ll see the declaration of the interface Array, as shown below.

The <T> in line 1008 is a placeholder for a concrete type that must be provided by the application developer during the declaration of the array like we did earlier. TypeScript requires you to declare a type parameter with Array, and whenever you’ll be adding new elements to this array, the compiler will check that their type matches the type used in the declaration.

In our code samples, we used the concrete type <Person> as a replacement of the generic parameter represented by the letter <T>:

const workers: Array<Person>;

But because generics aren’t supported in JavaScript, you won’t see them in the code generated by the transpiler – generics (as any other types) are erased. Using type parameters is just an additional safety net for developers at compile time.

You can see more generic types T in lines 1022 and 1026 in the above figure. When generic types are specified with the function arguments, no angle brackets are needed, and you’ll see this syntax in the next listing. There’s no T type in TypeScript. The There means the push() and pop() methods let you push or pop objects of the type provided during the array declaration. For example, in the following code snippet, we declared an array using the type Person as a replacement of T and that’s why we can use the instance of Person as the argument of the method push():

const workers: Array<Person>;
workers.push(new Person());
Note

The letter T stands for type, which is intuitive, but any letter or word can be used for declaring a generic type. In a map, developers often use the letter K for key and V for value.

Seeing the type T in the API of the interface Array tells us that its creator enabled support of generics. In one of the future blogs, I’ll show you how to create your own generic types.  Even if you’re not planning to create your own generic types, it’s really important that you understand the syntax of generics while reading someone else’s code of TypeScript documentation. You can also read our book TypeScript Quickly, where it’s explained.

TypeScript enums

This blog is a part of my TypeScript series, and the previous ones are:

1. Why program in TypeScript
2. Structural vs nominal typing
3. Getting started with TypeScript classes
4. Access modifiers public, private, and protected
5. Abstract classes

Enumerations (a.k.a. enums) allow you to create limited sets of named constants that have something in common. For example, a week has seven days, and you can assign numbers from 1 to 7 to represent them. But what’s the first day of the week?

According to the standard ISO 8601, Monday is the first day of the week, which doesn’t stop such countries as USA, Canada, and Australia consider Sunday as the first day of the week. Hence using just numbers from 1 to 7 for representing days may not be a good idea. Also, what if someone will assign the number 8 to the variable that store the day? We don’t want this to happen and using day names instead of the numbers makes our code more readable.

On the other hand, using numbers for storing days is more efficient than their names. So we want to have readability, the ability to restrict values to a limited set, and efficiency in storing data. This is where enums can help.

TypeScript has the enum keyword that can define a limited set of constants, and we can declare the new type for weekdays as follows:

enum Weekdays {
  Monday = 1,
  Tuesday = 2,
  Wednesday = 3,
  Thursday = 4,
  Friday = 5,
  Saturday = 6,
  Sunday = 7
}

Here, we define a new type Weekdays that has a limited number of values. We initialized each enum member with a numeric value, and a day of the week can be referred using the dot notation:

let dayOff = Weekdays.Tuesday;

The value of the variable dayOff is 2, but if you’d be typing the above line in your IDE or in TypeScript Playground, you’d be prompted with the possible values as shown on the next screenshot.

Using the members of the enum Weekdays stops you from making a mistake and assigning a wrong value (e.g. 8) to the variable dayOff. Well, strictly speaking, nothing stops you from ignoring this enum and write dayOff = 8 but this would be a misdemeanor.

We could initialize only Monday with 1, and the rest of the days values will be assigned using auto-increment, e.g. Tuesday will be initialized with 2, Wednesday with 3 and so on.

enum Weekdays {
  Monday = 1,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday
}

By default, enums are zero-based, and if we wouldn’t initialize the Monday member with one, its value would be zero.

Reversing numeric enums

If you know the value of the numeric enum, you can find the name of the corresponding enum member. For example, you may have a function that returns the weekday number and you’d like to print its name. By using this value as an index, you can retrieve the name of the day.

enum Weekdays {  // Declaring a numeric enum
  Monday = 1,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday
}

const getDay = () => 3;  // A function that returns 3
const today = getDay();  // today is equal 3

console.log(Weekdays[today]);  // Getting the name on the member that’s equal to 3 

In the last line, we retrieve the name of the day, and it’ll print Wednesday on the console.

In some cases, you don’t even care which numeric values are assigned to the enum members, and the following function convertTemperature() illustrates this. It converts the temperature from Fahrenheit to Celsius or visa versa. In this version of convertTemperature(), we won’t use enums, but then will re-write it with them. This function takes two parameters: temperature and conversion direction

function convertTemperature(temp: number, fromTo: string): number { 

  return ('FtoC' === fromTo) ?
      (temp - 32) * 5.0/9.0:  // Convert from Fahrenheit to Celsius
      temp * 9.0 / 5.0 + 32;  //Convert from Celsius to Fahrenheit
}

console.log(`70F is ${convertTemperature(70, 'FtoC')}C`);  
console.log(`21C is ${convertTemperature(21, 'CtoF')}F`);  
console.log(`35C is ${convertTemperature(35, 'ABCD')}F`);  

This function converts the value from Celsius to Fahrenheit if you pass any value as a fromTo parameter except FtoC. In the last line, we purposely provided the erroneous value ABCD as a fromTo parameter, and this function still converts the temperature from Celsius to Fahrenheit. The attempts to invoke a function with the erroneous values should be caught by the compiler and this is what TypeScript enums are for. You can see it in action here.

In the next listing, we declare the enum Direction that restricts the allowed constants to either FtoC or CtoF and nothing else. We also changed the type of the fromTo parameter from a string to Direction.

enum Direction {  // Declaring the enum Direction 
  FtoC,
  CtoF
}

function convertTemperature(temp: number, fromTo: Direction): number {  

      return (Direction.FtoC === fromTo) ?
             (temp - 32) * 5.0/9.0:
             temp * 9.0 / 5.0 + 32;
}

console.log(`70F is ${convertTemperature(70, Direction.FtoC)}C`); 
console.log(`21C is ${convertTemperature(21, Direction.CtoF)}F`); 

Since the type of the second parameter of the function is Direction, we have to invoke this function providing one of this enum’s member, e.g. Direction.CtoF. We’re not interested in what is the numeric value of this member. The purpose of this enum is just to provide a limited set of constants: CtoF and FtoC. The IDE will prompt you with two possible values for the second parameter, and you won’t make a mistake providing a random value.

Enum members are initialized with values (either explicitly or implicitly). All examples included in this section had enum members initialized with numbers, but TypeScript allows you to create enums with string values, and we’ll see such examples next.

String enums

In some cases, you may want to declare a limited set of string constants, and you can use string enums for this, i.e. enums that have their members initialized with string values. Say you’re programming a computer game where the player can move in the following directions:

enum Direction {
    Up = "UP",       
    Down = "DOWN",   
    Left = "LEFT",   
    Right = "RIGHT", 
}

When you declare a string enum, you must initialize each member. You may ask, “Why not just use a numeric enum here so TypeScript would automatically initialize its members with any numbers?” The reason is that in some cases you want to give meaningful values to the enum members. For example, you need to debug the program and instead of seeing that the last move was 0, you’ll see that the last move was UP.

And the next question you may ask, “Why declare the enum Direction if I can just declare four string constants with the values UP, DOWN, LEFT, and RIGHT?” You can, but let’s say we have a function with the following signature:

move(where: string)

A developer can make a mistake (or a typo) and invoke this function as move(“North”). But North is not a valid direction, and it’s safer to declare this function using the enum Direction:

move(where: Direction)

As you see, wrong argument value is caught by the compiler (1), and auto-complete (2) prevents mistakes

We made a mistake and provided a string “North” in line 15, and the compile-time error would read “Argument of type ‘”North”‘ is not assignable to the parameter of type ‘Direction’.” In line 20, the IDE offers you a selection of valid enum members so there’s no way you provide the wrong argument.

Now, let’s imagine that you need to keep track of the app state changes. The user can initiate a limited number of actions in each of the views of your app. Say, you want to log the actions taken in the view Products. Initially, the app tries to load products, and this action can either succeed or fail. The user can also search for products. To represent the states of the view Products you may declare a string enum as follows:

enum ProductsActionTypes {
  Search = 'Products Search', 
  Load = 'Products Load All', 
  LoadFailure = 'Products Load All Failure', 
  LoadSuccess = 'Products Load All Success' 
}

// If the function that loads products fails...
console.log(ProductsActionTypes.LoadFailure);  

When the user clicks on the button to load products, you can log the value of the member ProductsActionTypes.Load, which will log the text ‘Products Load All’. If the products were loaded successfully, log the value of ProductsActionTypes.LoadFailure, which will log the text ‘Products Load All Failure’.

Note: Some state management frameworks (e.g. Redux) require the app to emit actions when the app state changes. If we’d declare a string enum like in the above listing, we’d be emitting actions ProductsActionTypes.Load, ProductsActionTypes.LoadSuccess et al.

Note: String enums are not reversible, and you can’t find the member’s name if you know its value.

const enums

If you use the keyword const while declaring enum, its values will be inlined and no JavaScript will be generated. Let’s compare the generated JavaScript of enum vs const enum. The left side of the following screenshot shows the enum declared without the const, and the right side shows the generated JavaScript. For illustration purposes, in the last line, we just print the next move.

Now let’s just add the keyword const on the first line before the enum, and compare the generated JavaScript (on the right) with the one from the screenshot above.

As you see, the JavaScript code for the enum Direction was not generated – it was erased. But the values of the enum member that was actually used in the code (i.e. Direction.Down) was inlined in JavaScript.

Using const with enum results in more concise JavaScript, but keep in mind that since there is no JavaScript code that would represent your enum, you may run into some limitations, e.g. you won’t be able to reverse the numeric enum member name by its value.

Overall, with enums, the readability of your programs increases. Besides, using enum is the step in the right direction: moving away from the type any.

If you like this blog series, consider reading our book “TypeScript Quickly”. So far, only 175 pages are available, but the publisher will be releasing the new content monthly.
An extra bonus: this book will not only introduce you to TypeScript programming, but you’ll also learn how the blockchain technology works while going over multiple sample apps.

Abstract classes in TypeScript

This blog is a part of my TypeScript series, and the previous ones are:

1. Why program in TypeScript
2. Structural vs nominal typing
3. Getting started with TypeScript classes
4. Access modifiers public, private, and protected

If you add the abstract keyword to the class declaration, it can’t be instantiated. An abstract class may include methods that are implemented as well as the abstract ones that are only declared.

And why would you even want to create a class that can’t be instantiated? The reason is that you may want to include some non-implemented methods in a class, but you want to make sure that these methods will be implemented in its subclasses. This concept is easier to grasp while working on a specific task, so let’s consider a coding assignment and see how abstract classes can be used there.

An assignment with abstract classes

A company has employees and contractors. Design the classes to represent workers of this company. Any worker’s object should support the following methods:

constructor(name: string)

changeAddress(newAddress: string)

giveDayOff()

promote(percent: number)

increasePay(percent: number)

In our scenario, to promote means giving one day off and raising the salary by the specified percent. The method increasePay() should raise the yearly salary for employees but increase the hourly rate for contractors. How do you implement methods is irrelevant; a method can just have one console.log() statement.

Let’s work on this assignment. We’ll need to create the classes Employee and Contractor, which should have some common functionality. For example, changing the address and giving a day off should work the same way for contractors and employees, but increasing pay requires different implementation for these categories of workers.

Here’s the plan: we’ll create the abstract class Person with two descendants: Employee and Contractor. The class Person will implement methods changeAddress(), giveDayOff(), and promote(). This class will also include a declaration of the abstract method increasePay(), which will be implemented (differently!) in the subclasses of Person shown next.

abstract class Person {  // 1

constructor(public name: string) { };

changeAddress(newAddress: string ) {  // 2
   console.log(`Changing address to ${newAddress}`);
}

giveDayOff() {  // 2
console.log(`Giving a day off to ${this.name}`);
}

promote(percent: number) {  // 2
   this.giveDayOff();
   this.increasePay(percent);  // 3
}

abstract increasePay(percent: number);  // 4

}

1. Declaring an abstract class
2. Declaring and implementing a method
3. “Invoking” the abstract method
4. Declaring an abstract method

If you don’t want to allow invoking the method giveDayOff() from external scripts, add private to its declaration. If you want to allow invoking giveDayOff() only from the class Person and its descendants, male this method protected.

Note that you are allowed to write a statement that “invokes” the abstract method. Since the class is abstract, it can’t be instantiated, and there is no way that the abstract (unimplemented method) will be actually invoked. If you want to create a descendant of the abstract class that can be instantiated, you must implement all abstract methods of the ancestor. The code in the next listing shows how we implemented the classes Employee and Constructor.

class Employee extends Person {

  increasePay(percent: number) {
    console.log(`Increasing the salary of ${this.name} by ${percent}%`);  // 1
  }
}

class Contractor extends Person {
  increasePay(percent: number) {
    console.log(`Increasing the hourly rate of ${this.name} by ${percent}%`);  // 2
  }
}

1. Implementing the method increasePay() for employees
2. Implementing the method increasePay() for contractors

The next listing shows how can we see our solution in action. We’ll create an array of workers with one employee and one contractor and then iterate through this array invoking the method promote() on each object.

const workers: Person[] = [];  // 1

workers[0] = new Employee('John');
workers[1] = new Contractor('Mary');

workers.forEach(worker =&gt; worker.promote(5));  // 2

1. Declaring an array of the superclass type
2. Invoking promote() on each object

The workers array is of type Person, which allows us to store there the instances of descendant objects as well.

Since the descendants of Person don’t declare their own constructors, the constructor of the ancestor will be invoked automatically when we instantiate Employee and Contractor. If any of the descendants declared its own constructor, we’d have to use super() to ensure that the constructor of the Person is invoked.

You can run this code sample in the TypeScript playground, and the browser console will show the following output:

Giving a day off to John
Increasing the salary of John by 5%
Giving a day off to Mary
Increasing the hourly rate of Mary by 5%

The previous code fragment gives an impression that we iterate through the objects of type Person invoking Person.promote(). But realistically, some of the objects can be of type Employee while others are instances of Contractor. The actual type of the object is evaluated only during runtime, which explains why the correct implementation of the increasePay() is invoked on each object. This is an example of polymorphism – a feature that each object-oriented language supports.

Protected constructors

In the previous blog, we declared a private constructor to create a singleton. There’s some use for protected constructors as well. Say you need to declare a class that can’t be instantiated, but its subclasses can. Then, you could declare a protected constructor in the superclass and invoke it using the method super() from the subclass constructor.

This mimics one of the features of abstract classes. But a class with protected constructor wouldn’t let you declare abstract methods unless the class itself is declared as abstract.

TypeScript access modifiers public, private, protected

This blog is a part of my TypeScript series, and the previous ones are:

1. Why program in TypeScript
2. Structural vs nominal typing
3. Getting started with TypeScript classes

TypeScript includes the keywords public, protected, and private to control access to the members of a class i.e. properties or methods.

Class members marked public can be accessed from the internal class methods as well as from the external scripts. This is a default access.

Class members marked as protected can be accessed either from the internal class methods or from its descendants.

The private class members can be accessed from within the class only.

NOTE: If you know languages like Java or C#, you may already know the concept of restricting the access level with private and protected keywords. But TypeScript is a superset of JavaScript, which doesn’t support the private keyword, so the keywords private and protected (as well as public) are removed during the code compilation. The resulting JavaScript won’t include these keywords and you can consider them just as a convenience during development.

The next screenshot illustrates the protected and private access level modifiers. In line 15, the code can access the protected ancestor’s method sayHello(), because its done from the descendant. But when we clicked Ctrl-Space after this. In line 21, the variable age is not shown in the auto–complete list because it’s declared as private and can be accessed only within the class Person.

This code sample shows that the subclass can’t access the private member of the superclass. In general, only a method from the class Person can access private members from this class.

To try this code on your own, visit the TypeScript playground here.

While protected class members are accessible from the descendant’s code, they are not accessible on the class instance. For example, the following code won’t compile and will give you the error “Property ‘sayHello’ is protected and only accessible within class ‘Person’ and its subclasses”:

const empl = new Employee(); +
empl.sayHello(); // error

DISCLAMER: IMO, the protected access level is useless in any programming language. I explained why they are useless in Java back in 2006. Then I continued my witch hunt against seemengly protected creatures in the Adobe Flex framework. As I’m getting older, my motivation to fight protected variables is not as strong as it used to be. Live and let live.

Let’s look at another example of the class Person, which has a constructor, two public and one private property. First, we’ll write a long version of the class declaration.

class Person {
    public firstName: string;
    public lastName: string;
    private age: number;

    constructor(firstName:string, lastName: string, age: number) {
        this.firstName = firstName;
        this.lastName;
        this.age = age;
    }
}

The constructor in the class Person performs a tedious job of assigning the values from its argument to the respective members of this class. By using access qualifiers with the constructor’s arguments, you instruct the TypeScript compiler to create class properties having the same names as constructor’s arguments. The compiler will auto-generate the JavaScript code to assign the values given to the constructor to class properties. This will make the code of the TypeScript class more concise as shown in the next screenshot.

TIP: If you’d use just the readonly qualifier with constructor arguments, TypeScript compiler would also create read-only class variables for them.

In line 8, I create an instance of the class Person passing the initial property values to its constructor, which will assign these values to the respective object’s properties. In line 10, I wanted to print the values of the object’s properties firstName and age, but the latter is marked with a red squiggly line. If you hover the mouse over the erroneous fragment, you’ll see that the TypeScript’s static analyzer (it runs even before the compiler) properly reports an error:

Property age is private and only accessible within class Person.

In the TypeScript playground, the JavaScript code is generated anyway because from the JavaScript perspective, the code in line 10 is perfectly fine. But in your projects, you should always use the compiler’s option noEmitOnError to prevent the generation of JavaScript until all TypeScript syntax errors are fixed.

Implementing a singleton with a private constructor

Imagine, you need to create a single place that serves as a storage of important data in memory representing the current state of the app. Various scripts can have an access to this storage but you want to make sure that only one such object can be created for the entire app, also known as a single source of truth.

Singleton is a popular design pattern that restricts the instantiation of a class to only one object. How do you create a class that you can instantiate only once? It’s a rather trivial task in any object-oriented language that supports the private access qualifier.

Basically, you need to write a class that won’t allow using the new keyword, because with the new, you can create as many instances as you want. The idea is simple – if a class has a private constructor, the operator new will fail.

Then, how to create even a single instance of the class? The thing is that if the class constructor is private, you can access if only within the class, and as the author of this class, you’ll responsibly create it only once by invoking that same new operator from the class method.

But can you invoke a method on a class that was not instantiated? Of course, you can! JavaScript (as well as its big brother TypeScript) support static class members, which are shared between multiple instances of the class.

The next listing shows our implementation of the singleton design pattern in a class AppState, which has the property counter. Let’s assume that the counter represents our app state, which may be updated from multiple scripts in the app.

Any of such scripts must update the only place that stores the value of the counter, which is the singleton instance of AppState. Any script that needs to know the latest value of the counter will also get it from the AppState instance.

The class AppState has a private constructor, which means that no other script can instantiate it using the statement new. It’s perfectly fine to invoke such a constructor from within the AppState class, and we do this in the static method getInstance().

The method getInstance() is static, and this is the only way we can invoke a method in the absence of the class instance.

class AppState {

    counter = 0;  
    private static instanceRef: AppState;

    private constructor() { }

    static getInstance(): AppState {
        if (AppState.instanceRef === undefined) {
            AppState.instanceRef = new AppState();
        } 

        return AppState.instanceRef; 
    }
}

// const appState = new AppState(); // error because of the private constructor

const appState1 = AppState.getInstance(); 

const appState2 = AppState.getInstance();

appState1.counter++;
appState1.counter++;
appState2.counter++;
appState2.counter++;

console.log(appState1.counter); // prints 4 
console.log(appState2.counter); // prints 4

Both console.log() invocations will print 4 as there is only one instance of AppState.

To see this code sample in CodePen, visit this page.

Getting started with TypeScript classes

This blog is a part of my TypeScript series, and the previous ones are:

1. Why program in TypeScript
2. Structural vs nominal typing

In the next several blogs, I’ll focus on object-oriented features of Typescript – classes and interfaces. Let’s start with classes.

First of all, let me remind you that starting from the ECMAScript 2015 spec (a.k.a. ES6), JavaScript supports classes. Being a superset of JavaScript, TypeScript supports all features from JavaScript classes and adds some extras.

For example, JavaScript doesn’t offer syntax for declaring class properties, but TypeScript does. In the screenshot below on the left, you can see how I declared and instantiated the class Person that has three properties. The right side shows the ES6 version of this code produced by the TypeScript compiler:

As you see, there are no properties in the JavaScript version of the class Person. Also, since the class Person didn’t declare a constructor, we had to initialize its properties after instantiating. A constructor is a special function that’s executed only once when the instance of a class is created.

Declaring a constructor with three arguments would allow you to instantiate the class Person and initialize its properties in one line. In TypeScript, you can provide type annotation for constructor’s arguments, but there’s more.

TypeScript offers access level qualifiers public, private, and protected (I’ll cover these in the future blog), and if you use any of them with the constructor arguments, the TypeScript will generate the code for adding these properties in a JavaScript object as in the following screenshot:

Now the code of the TypeScript class (on the left) is more concise and the generated JavaScript code creates three properties in the constructor. We’d like to bring your attention to line 6 in the above screenshot on the left. We declared the constant without specifying its type, but we could have re-written this line explicitly specifying the type of p as follows:

const p: Person = new Person("John", "Smith", 25);

This illustrates an unnecessary use of an explicit type annotation. Since you declare a constant and immediately initialize it with an object of a known type (i.e. Person), the TypeScript type checker can easily infer and assign the type to the constant p. The generated JavaScript code will look the same with or without specifying the type of p. You can try it in the TypeScript playground by following this link.

Regardless if a class has a constructor or it doesn’t, classes allow you to declare custom types that didn’t exist in TypeScript before.

NOTE: We use the public access level with each constructor argument in the TypeScript class, which simply means that the generated corresponding properties can be accessed from any code located both inside and outside of the class.

When you declare the properties of a class, you can also mark them as readonly. Such properties can be initialized either at the declaration point or in the class constructor, and their values can’t be changed afterwards. The readonly qualifier is similar to the const keyword, but the latter can’t be used with class properties.

Getting familiar with class inheritance

In real life, every person inherits some features from his or her parents. Similarly, in the TypeScript world, you can create a new class, based on the existing one. For example, you can create a class Person with some properties and then the class Employee that will _inherit_ all the properties of Person and declare some additional ones.

Inheritance is one of the main features of any object-oriented language. TypeScript has the keyword extends to declare that one class is inherited from the other.

The line 7 in the following screenshot shows how to declare an Employee class that extends the class Person and declares an additional property department. In line 11, we create an instance of the class Employee.

This screenshot was taken from the playground at typscriptlang.org after we entered empl. followed by CTRL-Space on line 13. The TypeScript’s static analyzer recognizes that the type Employee is inherited from Person so it suggests the properties defined in both classes Person and Employee.

In our example, the class Employee is a subclass of Person. Accordingly, the class Person is a superclass of Employee. You can also say that the class Person is an ancestor and Employee is a descendant of Person.

NOTE: Under the hood, JavaScript supports prototypal _object-based_ inheritance, where one object can assign another object as its prototype, and this happens during the runtime. During transpiling to JavaScript, the generated code uses the syntax of the prototypal inheritance as seen in the above screenshot on the right.

In addition to properties, a class can include _methods_ – this is how we call functions declared inside the classes. And if a method(s) is declared in a superclass, it will be inherited by the subclass unless the method was declared with the access qualified private which we’ll discuss a bit later.

The next version of the class Person is shown in the screenshot below, and it includes the method sayHello(). As you can see in line 17, TypeScript included this method in its typeahead help.

You may be wondering, “Is there any way to control which properties and methods of a superclass are accessible from the subclass?”
Actually, a more general question would be “Is there any way to control which properties and methods of a class are accessible from other scripts?” The answer is yes – this is what the private, protected, and public keywords are for, and I’ll cover them in the next blog. Stay tuned…