Categories
JavaScript TypeScript

Multiple Inheritance with TypeScript Mixins

In JavaScript, there’s no easy way to inherit from multiple classes. We can make our own mixins to inherit from multiple objects. This is made easier in TypeScript by making mixins a standard. With TypeScript, we can make interfaces that extend multiple classes or interfaces. This way, we can reuse multiple partial classes to create a new child class. We do this with mixins and copy over the properties to a new class that derive members from parent classes with our own function.

To define a interfaces that inherit from multiple classes in TypeScript, we create an interface that extends multiple classes or interfaces. Each of these classes or interfaces is called a mixin. We can mix and match them to create a combined interface to have whatever properties that we want. For example, we can write something like the following code:

class Animal {  
    species: string;  
    constructor(species: string) {  
        this.species = species  
    }  
}

class Person {  
    name: string;  
    constructor(name: string) {  
        this.name = name  
    }  
}

interface Employee extends Person, Animal {  
    employeeCode: string;  
}

let employee: Employee = {  
    species: 'human',  
    name: 'Joe',  
    employeeCode: '123'  
}

In the code above, we made an Employee interface which inherits all the members from the Animal and Person classes and we incorporated the employeeCode member into the Employee interface. This lets us create an Employee object with the species , name , and employeeCode properties.

We can only use the extends keyword with multiple classes or interfaces if we use the keyword in the interfaces. It won’t work if we use it in a class. For example, if we write:

class Animal {  
    species: string;  
    constructor(species: string) {  
        this.species = species  
    }  
}

class Person {  
    name: string;  
    constructor(name: string) {  
        this.name = name  
    }  
}

class Employee extends Person, Animal {  
    employeeCode: string;  
}

let employee: Employee = {  
    species: 'human',  
    name: 'Joe',  
    employeeCode: '123'  
}

Then we get the error message:

Classes can only extend a single class.(1174)

We can also inherit from interfaces, along with classes in an interface like we do in the code below:

class Animal {  
    species: string;  
    constructor(species: string) {  
        this.species = species  
    }  
}

class Person {  
    name: string;  
    constructor(name: string) {  
        this.name = name  
    }  
}

interface Job {  
    title: string;  
}

interface Employee extends Person, Animal, Job {  
    employeeCode: string;  
}

let employee: Employee = {  
    species: 'human',  
    name: 'Joe',  
    employeeCode: '123',  
    title: 'laborer'  
}

As we can see, interfaces are very flexible, we can inherit from different interfaces and classes whatever way we want, unlike classes. Each of our classes is called a mixin.

If we have overlapping properties in our mixins, then they’ll be combined together with declaration merging operations done by TypeScript. For example, if we have 2 classes with the some overlapping members like we have in the code below as long as the overlapping members are identical:

class Animal {  
    species: string;  
    id: number = 0;  
    constructor(species: string) {  
        this.species = species  
    }  
}

class Person {  
    name: string;  
    id: number = 0;  
    constructor(name: string) {  
        this.name = name  
    }  
}

interface Job {  
    title: string;  
}

interface Employee extends Person, Animal, Job {      
    employeeCode: string;  
}

let employee: Employee = {  
    id: 1,  
    species: 'human',  
    name: 'Joe',  
    employeeCode: '123',  
    title: 'laborer'  
}

As we can see, we have an id member in both the Person and Animal interfaces and they’re both of the number type. Overlapping members with the same name and type are allowed for multiple inheritance. However, overlapping members with the same name but different types aren’t allowed. For example, if we have:

class Animal {  
    species: string;  
    id: string = '';  
    constructor(species: string) {  
        this.species = species  
    }  
}

class Person {  
    name: string;  
    id: number = 0;  
    constructor(name: string) {  
        this.name = name  
    }  
}

interface Job {  
    title: string;  
}

interface Employee extends Person, Animal, Job {      
    employeeCode: string;  
}

let employee: Employee = {  
    id: 1,  
    species: 'human',  
    name: 'Joe',  
    employeeCode: '123',  
    title: 'laborer'  
}

Then we get the error message:

Interface 'Employee' cannot simultaneously extend types 'Person' and 'Animal'.Named property 'id' of types 'Person' and 'Animal' are not identical.(2320)

since we id in the Animal class is a string , but id in the Person class is a number.

Copying Implementation Parent Classes to the Derived Class

To copy the mixin methods into a new class. We can write the following function to copy the methods from the parent class into a new class. The function has to loop through the base classes and get the content inside the classes and then define new properties in the class derived from the parent classes and then set them one by one in the new class. This is works because classes are just syntactic sugar for constructor objects that are in JavaScript since the early days.

We can write the following function to achieve what we want:

function applyMixins(derivedConstructor: any, baseConstructors: any[]) {  
    baseConstructors.forEach(baseConstructor => {  
      Object.getOwnPropertyNames(baseConstructor.prototype)  
      .forEach(name => {  
        Object.defineProperty(derivedConstructor.prototype,   
           name,  
           Object.  
             getOwnPropertyDescriptor(  
               baseConstructor.prototype,   
               name  
             )  
           );  
        });  
    });  
}

The applyMixin does exactly what we described above. Now we just have to call it with the child class that inherits the parent classes as the first argument and then an array with the parent classes as the second argument. We call it as in the following code:

applyMixins(Employee, [Person, Animal])

Then given that we have the following classes defined:

class Animal {  
    species: string;  
    id: number = 0;  
    constructor(species: string) {  
        this.species = species  
    }  
    eat(){}  
}

class Person {  
    name: string;  
    id: number = 0;  
    constructor(name: string) {  
        this.name = name  
    }  
    speak(){}  
}

interface Employee extends Person, Animal {      
    employeeCode: string;  
}

class Employee {      
}

We should get that the prototype of Employee having the eat and speak methods. We can call eat directly on a Employee object like the following code:

let employee: Employee = new Employee();  
employee.eat();

We can define our mixins with our class notation to let us do multiple inheritance with TypeScript. Then we define an interface that specifies which mixins we inherit from. Once we did that, we copy over the members that are in the parent classes to the child class’ prototype. Then we have all the properties of the parent classes accessible from the child class.

Categories
JavaScript TypeScript

TypeScript Advanced Types — this Type and Dynamic Types

TypeScript has many advanced type capabilities, which makes writing dynamically typed code easy. It also facilitates the adoption of existing JavaScript code since it lets us keep the dynamic capabilities of JavaScript while using the type-checking capability of TypeScript. There are many kinds of advanced types in TypeScript, like intersection types, union types, type guards, nullable types, and type aliases, and more. In this article, we’ll look at the this type and creating dynamic types with index signatures and mapped types.

This Type

In TypeScript, we can use this as a type. It represents the subtype of the containing class or interface. We can use it to create fluent interfaces easily since we know that each method in the class will be returning the instance of a class.

For example, we can use it to define a class with chainable methods like in the following code:

class StringAdder {  
  value: string = '';  
  getValue(): string {  
    return this.value;  
  } 

  addFoo(): this {  
    this.value += 'foo';  
    return this;  
  } 

  addBar(): this {  
    this.value += 'bar';  
    return this;  
  } 

  addGreeting(name: string): this {  
    this.value += `Hi ${name}`;  
    return this;  
  }  
}
const stringAdder: StringAdder = new StringAdder();  
const str = stringAdder  
  .addFoo()  
  .addBar()  
  .addGreeting('Jane')  
  .getValue();  
console.log(str);

In the code above, the addFoo, addBar, and addGreeting methods all return the instance of the StringAdder class, which lets us chain more method calls of the instance to it once it’s instantiated. The chaining is made possible by the this return type that we have in each method.

Index Types

To make the TypeScript compiler check code with dynamic property names, we can use index types. We can use the extends keyof keyword combination to denote that the type has the property names of another type. For example, we can write:

function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {  
  return propNames.map(n => o[n]);  
}

Then we can use the choose function as in the following code:

function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {  
  return propNames.map(n => o[n]);  
}

const obj = {  
  a: 1,  
  b: 2,  
  c: 3  
}  
choose(obj, ['a', 'b'])

Then we get the values:

[1, 2]

if we log the results of the choose function. If we pass in a property name that doesn’t exist in the obj object into the array in the second of the choose function, then we get an error from the TypeScript compiler. So if we write something like the following code:

function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {  
  return propNames.map(n => o[n]);  
}const obj = {  
  a: 1,  
  b: 2,  
  c: 3  
}  
const arr = choose(obj, ['d']);

Then we get the error:

Type 'string' is not assignable to type '"a" | "b" | "c"'.(2322)

In the examples above, keyof U is the same as the string literal type “a” | “b” | “c” since we passed in the type of the generic U type marker where the actual type is inferred from the object that we pass in into the first argument. The K extends keyof U part means that the second argument must have an array of some or all the key names of whatever is passed into the first argument, which we denoted by the generic U type. Then we defined the return type as an array of values that we get by looping through the object we pass into the first argument, hence we have the U[K][] type. U[K][] is also called the index access operator.

Index types and Index Signatures

An index signature is a parameter that must be of type string or number in a TypeScript interface. We can use it to denote the properties of a dynamic object. For example, we can use it like we do in the following code:

interface DynamicObject<T> {  
  [key: string]: T;  
}  
let obj: DynamicObject<number> = {  
  foo: 1,  
  bar: 2  
};  
let key: keyof DynamicObject<number> = 'foo';  
let value: DynamicObject<number>['foo'] = obj[key];

In the code above, we defined a DynamicObject<T> interface which takes a dynamic type for its members. We have an index signature called the key which is a string. It can also be a number. The type of the dynamic members is denoted by T, which is a generic type marker. This means that we can pass in any data type into it.

Then we defined the obj object, which is of type DyanmicObject<number>. This makes use of the DynamicObject interface we created earlier. Then we defined the key variable, which has the type keyof DynamicObject<number>, which means that it has to be a string or a number. This means that the key variable must have one of the property names as the value. Then we defined the value variable, which must have the value of an object of type DynamicObject .

This means that we can’t assign anything other than a string or number to the key variable. So if write something like:

let key: keyof DynamicObject<number> = false;

Then we get the following error message from the TypeScript compiler:

Type 'false' is not assignable to type 'string | number'.(2322)

Mapped Types

We can create a new type by mapping the members of an existing type into the new type. This is called a mapped type.

We can create mapped types like we do in the following code:

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

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

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

type ReadOnlyPerson = ReadOnly<Person>;  
type PartialPerson = PartialType<Person>;let readOnlyPerson: ReadOnlyPerson = {  
  name: 'Jane',  
  age: 20  
}
readOnlyPerson.name = 'Joe';  
readOnlyPerson.age = 20;

In the code above, we created the ReadOnly type alias to let us map the members of an existing type into a new type by setting each member of the type as readonly. This isn’t a new type on its own since we need to pass in a type to the generic type marker T . Then we create an alias for the types that we defined by passing in the Person type into the ReadOnly alias and Partial alias respectively.

Next we defined a ReadOnlyPerson object with the name and age properties set. Then when we try to set the values again, then we get the following errors:

Cannot assign to 'name' because it is a read-only property.(2540)Cannot assign to 'age' because it is a read-only property.(2540)

Which means that the readonly property from the ReadOnly type alias is being enforced. Likewise, we can do the same with the PartialType type alias. We have defined the PartialPerson type by mapping the members of the Person type to the PartialPerson type with the PartialPerson type. Then we can define a PartialPerson object like in the following code:

let partialPerson: PartialPerson = {};

As we can see, we can omit properties from the partialPerson object we as want.

We can add new members to the mapped type alias by creating an intersection type from it. Since we used the type keyword to define the mapped types, they’re actually actually types. They are actually type aliases. This means that we can’t put members straight inside, even though they look like interfaces.

To add members, we can write something like the following:

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

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

type ReadOnlyEmployee = ReadOnly<Person> & {  
  employeeCode: string;  
};

let readOnlyPerson: ReadOnlyEmployee = {  
  name: 'Jane',  
  age: 20,  
  employeeCode: '123'  
}

Readonly<T> and Partial<T> are included in the TypeScript standard library. Readonly maps the members of the type that we pass into the generic type placeholder into read-only members. The Partial keyword lets us map members of a type, into nullable members.

Conclusion

In TypeScript, we can use this as a type. It represents the subtype of the containing class or interface. We can use it to create fluent interfaces easily since we know that each method in the class will be returning the instance of a class. An index signature is a parameter that must be of type string or number in a TypeScript interface.

We can use it to denote the properties of a dynamic object. To convert members of a type to add some attributes to them, we can map the members of an existing type into the new type to add the attributes to the interface with mapped types.

Categories
JavaScript TypeScript

Introduction to TypeScript Classes — More Access Modifiers

Classes in TypeScript, like JavaScript are a special syntax for its prototypical inheritance model that is a comparable inheritance in class-based object oriented languages. Classes are just special functions added to ES6 that are meant to mimic the class keyword from these other languages. In JavaScript, we can have class declarations and class expressions, because they are just functions. So like all other functions, there are function declarations and function expressions. This is the same with TypeScript. Classes serve as templates to create new objects. TypeScript extends the syntax of classes of JavaScript and then add its own twists to it. In this article, we’ll look at more access modifiers for class members in TypeScript.

Readonly modifier

With TypeScript, we can mark a class member as read only with the readonly keyword. This prevents a member from being modified once it’s been initialized. Also, they must be initialized at their declaration or in the constructor. For example, we can use it like in the following code:

class Person {  
  readonly name: string;  
  constructor(name: string) {  
    this.name = name;        
  }  
}

const person = new Person('Jane');

If we try assign another to it after a value has been set to name after initialization, then we would get an error. For example, if we write:

class Person {  
  readonly name: string;  
  constructor(name: string) {  
    this.name = name;        
  }  
}  
const person = new Person('Jane');  
person.name = 'Joe';

Then the TypeScript compiler won’t compile the code and we would get the error message “Cannot assign to ‘name’ because it is a read-only property.(2540)“

We can make the code above shorter by using parameter properties. With parameter properties, we can both declare a readonly member and assign it a value by just putting the member declaration in the signature of the constructor. Once we put it inside the parentheses, then we can both declare it and assign it a value at the same time. For example, instead of writing:

class Person {  
  readonly name: string;  
  constructor(name: string) {  
    this.name = name;        
  }  
}

const person = new Person('Jane');

We can instead write:

class Person {    
  constructor(readonly name: string) {      
  }  
}

const person = new Person('Jane');

Then when we run console.log on person , then we can see that the member name has been assigned the value ‘Jane’ without having to explicitly write code to assign it the value. We can also replace readonly , with public , private , or protected ; or combine readonly with public , private , or protected . For example, we can write the following code:

class Person {    
  constructor(private readonly name: string) {      
  } getName() {  
    return this.name;  
  }  
}  
const person = new Person('Jane');  
console.log(person.getName());

to get a private and readonly member called name , which retrieve the value of it in the getName method. We should get ‘Jane’ when we run the console.log line on the last line. Likewise, we can do the same with public or protected like in the following code:

class Person {    
  constructor(protected readonly name: string) {      
  }  
}

class Employee extends Person {    
  constructor(  
    public name: string,   
    private employeeCode: number  
  ){      
    super(name);  
  } 

  getEmployeeCode() {  
    return this.employeeCode;  
  }  
}  
const person = new Employee('Jane', 123);  
console.log(person.name);  
console.log(person.getEmployeeCode());

As we can see, we have parameter properties that with all kinds of access modifiers and the readonly keyword in both the Person and the Employee class. Then we can use the Employee constructor to assign all the values to all the members with the Employee constructor. Then when we get the values of the members either directly or through a method as in the case of the private employeeCode member, where we retrieved it through the getEmployeeCode method, then we can see that the values we expected are logged. We see that person.name is ‘Jane’, and person.getEmployeeCode() gets us 123.

Accessors

Like in JavaScript, we can add getter and setter methods into TypeScript classes. This prevents us from accidentally modifying public member values directly, and gives us more control for how member values are retrieved and set. To add getters and setters to a class, we can use the get and set keywords respectively. We put them in front of the method signature of the class to designate a method as a getter or a setter. For example, we can use it like in the following code:

class Person {  
  private _name: string = ''; get name(): string {  
    return this._name;          
  } 

  set name(newName: string) {  
    if (newName && newName.length < 5) {  
      throw new Error('Name is too short');        
    }  
    this._name = newName;  
  }  
}

let person = new Person();  
person.name = 'Jane Smith';  
console.log(person.name);  
person.name = 'Joe';

In the example above, we added a getter method with the get keyword. The name method is used for getting the _name field, which is private, so we can’t get the value of it without a getter method. To retrieve the value of this._name via our getter name method, we just use the person.name property to get it. Then we to set the value of this._name , we add a setter method with the set keyword and the method name name . In the name setter method, we pass in the parameter which let us assign a value to it with the assignment operator like we did in the third last line in the code above.

As we can see, we can put validation code in the set name method. This is one good reason to use getter and setter methods because we can control how values are set for individual class members. In the example above, if the value we assign has less than 5 characters, then we throw an errors which has the message ‘Name is too short’. This prevents us from assigning a value where the string is less than 5 characters. If we run the code, the first assignment expression:

person.name = 'Jane Smith';

Then we get ‘Jane Smith’ logged. When we try to assign it a value that has less than 5 characters like we did with:

person.name = 'Joe';

Then we get an error raised like we have indicated in the code.

Note that to use accessors, we have to compile our output to ES5 or higher. Compiling to ES3 isn’t supported, but this should be a problems with modern browsers. Also, accessors that has a get but no set are automatically inferred as readonly .

In TypeScript, we have the readonly modifier for class members so that they won’t be able to be set to a new value after they have been initialized. Also, TypeScript has the parameter properties features so that we don’t have to write code explicitly to assign values to variables via the constructor. If we add in the parameters in the constructor, then they’ll be set automatically when we instantiate the class with the new keyword. The accessor methods are useful for controlling how we get and set values of class members. We can designate getter and setter methods with the get and set keywords.

Categories
JavaScript TypeScript

TypeScript Advanced Types — Nullable Types and Type Aliases

TypeScript has many advanced type capabilities and which make writing dynamically typed code easy. It also facilitates the adoption of existing JavaScript code since it lets us keep the dynamic capabilities of JavaScript while using the type-checking capability of TypeScript. There are multiple kinds of advanced types in TypeScript, like intersection types, union types, type guards, nullable types, and type aliases, and more. In this article, we look at nullable types and type aliases.

Nullable Types

To let us assign undefined to a property with the --strictNullChecks flag on, TypeScript supports nullable types. With the flag on, we can’t assign undefined to type members that don’t have the nullable operator attached to it. To use it, we just put a question mark after the member name for the type we want to use.

If we have the strictNullChecks flag on and we set a value of a property to null or undefined , then like we do in the following code:

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

let p: Person = {  
  name: 'Jane',  
  age: null  
}

Then we get the following errors:

Type 'null' is not assignable to type 'number'.(2322)input.ts(3, 3): The expected type comes from property 'age' which is declared here on type 'Person'

The errors above won’t appear if we have strictNullChecks off and the TypeScript compiler will allow the code to be compiled.

If we have the strictNullChecks flag on and we want to be able to set undefined to a property as the value, then we can make the property nullable. For example, we can set a member of an interface to be nullable with the following code:

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

let p: Person = {  
  name: 'Jane',  
  age: undefined  
}

In the code above, we added a question mark after the age member in the Person interface to make it nullable. Then when we define the object, we can set age to undefined. We can’t still set age to null . If we try to do that, we get:

Type 'null' is not assignable to type 'number | undefined'.(2322)input.ts(3, 3): The expected type comes from property 'age' which is declared here on type 'Person'

As we can see, a nullable type is just a union type between the type that we declared and the undefined type. This also means that we can use type guards with it like any other union type. For example, if we want to only get the age if it’s defined, we can write the following code:

const getAge = (age?: number) => {  
  if (age === undefined) {  
    return 0  
  }  
  else {  
    return age.toString();  
  }  
}

In the getAge function, we first check if the age parameter is undefined . If it is, then we return 0. Otherwise, we can call the toString() method on it, which is available to number objects.

Likewise, we can eliminate null values with a similar kind of code, for instance, we can write:

const getAge = (age?: number | null) => {  
  if (age === null) {  
    return 0  
  }    
  else if (age === undefined) {  
    return 0  
  }  
  else {  
    return age.toString();  
  }  
}

This comes in handy because nullable types exclude null from being assigned with strictNullChecks on, so if we want null to be able to be passed in as a value for the age parameter, then we need to add null to the union type. We can also combine the first 2 if blocks into one:

const getAge = (age?: number | null) => {  
  if (age === null || age === undefined) {  
    return 0  
  }  
  else {  
    return age.toString();  
  }  
}

Type Aliases

If we want to create a new name for an existing type, we can add a type alias to the type. This can be used for many types, including primitives, unions, tuples, and any other type that we can write by hand. To create a type alias, we can use the type keyword to do so. For example, if we want to add an alias to a union type, we can write the following code:

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

interface Employee {  
  employeeCode: number;  
}

type Laborer = Person & Employee;  
let laborer: Laborer = {  
  name: 'Joe',  
  age: 20,  
  employeeCode: 100  
}

The declaration of laborer is the same as using the intersection type directly to type the laborer object, as we do below:

let laborer: Person & Employee = {  
  name: 'Joe',  
  age: 20,  
  employeeCode: 100  
}

We can declare type alias for primitive types like we any other kinds of types. For example, we can make a union type with different primitive types as we do in the following code:

type PossiblyNumber = number | string | null | undefined;  
let x: PossiblyNumber = 2;  
let y: PossiblyNumber = '2';  
let a: PossiblyNumber = null;  
let b: PossiblyNumber = undefined;

In the code above, the PossiblyNumber type can be a number, string, null or undefined . If we try to assign an invalid to it like a boolean as in the following code:

let c: PossiblyNumber = false;

We get the following error:

Type 'false' is not assignable to type 'PossiblyNumber'.(2322)

just like any other invalid value assignment.

We can also include generic type markers in our type aliases. For example, we can write:

type Foo<T> = { value: T };

Generic type aliases can also be referenced in the properties of a type. For example, we can write:

type Tree<T> = {  
  value: T;  
  left: Tree<T>;  
  center: Tree<T>;  
  right: Tree<T>;  
}

Then we can use the Tree type as we do in the following code:

type Tree<T> = {    
  value: T,  
  left: Tree<T>;  
  center: Tree<T>;  
  right: Tree<T>;  
}

let tree: Tree<string> = {} as Tree<string>;  
tree.value = 'Jane';tree.left = {} as Tree<string>  
tree.left.value = 'Joe';  
tree.left.left = {} as Tree<string>;  
tree.left.left.value = 'Amy';  
tree.left.right = {} as Tree<string>  
tree.left.right.value = 'James';tree.center = {} as Tree<string>  
tree.center.value = 'Joe';tree.right = {} as Tree<string>  
tree.right.value = 'Joe';console.log(tree);

The console.log for tree on the last line should get us:

{  
  "value": "Jane",  
  "left": {  
    "value": "Joe",  
    "left": {  
      "value": "Amy"  
    },  
    "right": {  
      "value": "James"  
    }  
  },  
  "center": {  
    "value": "Joe"  
  },  
  "right": {  
    "value": "Joe"  
  }  
}

Nullable types are useful is we want to be able to assign undefined to a property when strictNullChecks flag is on when in our TypeScript compiler configuration. It’s simply a union type between whatever type you have and undefined . It’s denoted by a question mark after the property name. This means we can use type guards with it like any other union type. Note that nullable types don’t allow null values to be assigned to it since nullable types are only needed when strictNullChecks flag is on. Type alias let us create a new name for types that we already have. We can also use generics with type alias, but we can’t use them as standalone types.

Categories
JavaScript TypeScript

Introduction to TypeScript Classes- Access Modifiers

Classes in TypeScript, like JavaScript are a special syntax for its prototypical inheritance model that is a comparable inheritance in class-based object oriented languages. Classes are just special functions added to ES6 that are meant to mimic the class keyword from these other languages. In JavaScript, we can have class declarations and class expressions, because they are just functions. So like all other functions, there are function declarations and function expressions. This is the same with TypeScript. Classes serve as templates to create new objects. TypeScript extends the syntax of classes of JavaScript and then add its own twists to it. In this article, we’ll look at how to define TypeScript classes and how they inherit from each other. In this article, we’ll look at the access modifiers for class members in TypeScript.

Public, private, and protected modifiers

Public

In TypeScript, class member can have access modifiers added to them. This lets us control the access of the members of class by different parts of the program outside of the class that the members are defined in. The default access modifier for class members in TypeScript is public . This means that class member that have no access modifiers will be designated as public members. For example, we can use the public modifier like in the following code:

class Person {  
  public name: string;  
  public constructor(name: string) {  
    this.name = name;  
  } 

  public getName(): string{  
    return this.name;  
  }  
}

const person = new Person('Jane');  
console.log(person.getName());  
console.log(person.name);

In the example above, we designated all the members in our Person class as public so that we can access them outside the Person class. We can call the getName method on the Person instance and also we can get the name field directly from outside the class. The public access modifier on the constructor method is extra because constructor should always be public so we can instantiate the class with it.

Private and Protected

When a member of a class is marked as private , then it can’t be accessed outside of its containing class. Protected members are only available from within sub-classes of the class that has the protected member and the class that has the member and is marked with the keyword protected. For example, if we have a private member in our class in like the following code:

class Person {  
  private name: string;  
  constructor(name: string) {  
    this.name = name;  
  } 

  public getName(): string{  
    return this.name;  
  }  
}

const person = new Person('Jane');  
console.log(person.getName());  
console.log(person.name);

Then we get an error when we try to access it like we did with the member name in the Person class that we have above. If we try to compile and run the code above, the TypeScript compiler will not compile the code and gives the error message “Property ‘name’ is private and only accessible within class ‘Person’.(2341)“ like we expect for private members.

TypeScript compares type by their structure for public members. If the 2 types have the same public members listed, then they’re marked as being compatible by TypeScript. However, for private and protected members, this isn’t the case. For private and protected members, for 2 classes to be considered equal, then both classes must have the same private and protected members from the same origin for them to be considered to be the same type. For example, if we have the following code:

class Person {  
  private name: string;  
  constructor(name: string) {  
    this.name = name;  
  }  
}

class Human {  
  private name: string;  
  constructor(name: string) {  
    this.name = name;  
  }  
}

const human: Human = new Person('Jane');

Then we would get the error “ Type ‘Person’ is not assignable to type ‘Human’. Types have separate declarations of a private property ‘name’.(2322)“. This means that because both the Person and Human have the same private member called name , that they can’t be considered the same type, so we can’t assign an instance of Person to a variable that’s of type Human . This is the same for protected members, so if we have the following code:

class Person {  
  protected name: string;  
  constructor(name: string) {  
    this.name = name;  
  }  
}

class Human {  
  protected name: string;  
  constructor(name: string) {  
    this.name = name;  
  }  
}

const human: Human = new Person('Jane');

We would get the same error. But if we change protected to public like we do in the code below, then it would work:

class Person {  
  public name: string;  
  constructor(name: string) {  
    this.name = name;  
  }  
}

class Human {  
  public name: string;  
  constructor(name: string) {  
    this.name = name;  
  }  
}

const human: Human = new Person('Jane');  
console.log(human.name);

If we run the code above, we’ll see ‘Jane’ logged from the console.log statement on the last line.

If we have private members in our classes, then they must be in the super-class for both classes for both classes to be considered equal. For example, we can write the following code to make the Person and Human class to be considered the same while having a common private member name for each class:

class Animal {  
  private name: string;  
  constructor(name: string) {  
    this.name = name;        
  }  
}

class Person extends Animal{  
  constructor(name: string) {  
    super(name);      
  }  
}

class Human extends Animal{  
  constructor(name: string) {  
    super(name);      
  }  
}

const human: Human = new Person('Jane');  
console.log(human);

In the code above, we have the Animal class that the private name member, and both the Person and Human classes extends the Animal class, so that the Human and Person will be considered equal since they don’t have separate implementations of the private member name , but rather, a common name member in the Animal class instead which they both inherit from. When we run console.log on human in the last line, we would see the Person object being logged.

Likewise, for protected members, we can do the something similar like in the following code:

class Animal {  
  protected name: string;  
  constructor(name: string) {  
    this.name = name;        
  }  
}

class Person extends Animal{  
  constructor(name: string) {  
    super(name);      
  } 

  getName() {  
    return this.name;  
  }  
}

class Human extends Animal{  
  constructor(name: string) {  
    super(name);      
  } 

  getName() {  
    return this.name;  
  }  
}

const human: Human = new Person('Jane');  
console.log(human.getName());

In the code above, we have the protected member name which can be accessed by its sub-classes Human and Person , so we can return the value of the name member with a getName method on each class and the value of the name member in the Animal class. We need this method because protected members are only available from within sub-classes of the class that has the protected member and the class that has the member. If we run the code above, we would get ‘Jane’ from the console.log output from the last line of the code above.

In TypeScript, class members can have access modifiers applied to them. Public is the default access modifier for members if nothing is specified. 2 class are considered equal if they both have the same public members, or that they inherit protected and private members from the same source and have the same public members.