TypeScript Tutorial: Beyond the Basics
In this TypeScript tutorial, the second in a three-part series, we explore how functions are created, how arrow functions work, the purpose of classes and why interfaces are important.
October 14, 2018
In Part 1 of our series on TypeScript, we learned about some basic data types like numbers, strings, arrays, etc. In this TypeScript tutorial we focus on cool features like classes, as well as interfaces and arrow functions, which were introduced in ECMAScript 6.
Functions
Functions are general building blocks inside a class that hold some business logic. Creating a function in TypeScript is similar to the process in JavaScript: You use the function keyword. However, in TypeScript you also have the option to define the type of parameters and return type of the function. Below is a basic implementation of function in TypeScript.
function getProductCode(productId: string): string {
return `CODE-${productId}`;
}
In the above example we’ve created a function called getProductCode, which accepts a single parameter of type string. The code : string before the brace mark represents that this function will return a string. If you do not wish to return anything you can simply specify void instead of string. This function is much more readable and understandable: It accepts parameters of type string and returns back a string.
Using Default and Optional Parameters in a Function
A default parameter is a parameter that has a default value assigned to it, whereas an optional parameter does not need to pass a value. Both them offer the same benefits but with a slight difference. Take a look at the code below.
function showProducts(productType: number = 1, productCode?: string): Array {
// Implement your business logic that uses the productType parameter and returns an array of IProducts.
return undefined;
}
Note: We are returning undefined for demonstration purpose only.
In the above code, you may see that productType has been assigned a value of 1 whereas productCode has a question mark symbol attached to it. productType is a default parameter and productCode is an optional parameter. If you wish to call this function, TypeScript allows you to call it without a parameter value assigned to productCode. Since value for productType parameter is not passed while calling the function, TypeScript passes the default value 1 in the business logic.
REST Parameters
REST parameters were introduced in ECMAScript 6. They enable passing multiple arguments to the function. But there are some conditions you need to follow: To create a REST parameter, add three periods before the parameter name. The REST parameter should always be the last parameter in the parameters list and it should always be an array of a type that you want. REST parameters are optional, which means that the code will work even if you do not pass any values to it. Here’s a simple example of implementing REST parameter in a function.
function doSomethingWithProducts(productType: number, ...productNames: string[]) {
// Implement your business logic
}
this.doSomethingWithProducts(5, ["Electronics", "Furnitures", "Crockeries"]);
this.doSomethingWithProducts(5);
Arrow Functions
If you’re familiar with C#, you may have heard of Lambda expressions. Well, TypeScript makes use of a similar syntax for its arrow functions. Arrow functions eliminate the requirement to type the word function while creating a function.
Here’s an example of how to create and use an arrow function, indicated by the lambda symbol (=>).
let getNewCode = (userCode: string, productCode: string): string => {
return `${userCode}-${productCode}`;
}
this.getNewCode("123", "456");
Classes
Classes are blueprints that define the shape and structure of an object. A class in TypeScript can hold any number of properties, functions, “getters” and “setters”; they’re extremely simple to create. With classes, you can perform encapsulation by assigning different access modifiers (such as public, private and protected in the code below). Below is an example of a class with some properties (with different access modifiers), functions and a constructor.
class Products {
public readonly id: number;
public name: string;
private productCode: string;
protected price: number;
constructor(code: string) {
this.productCode = code;
}
public getProductDetails(code: string): any {
return "";
}
private applyDiscount(code: string): void {
// business logic
}
}
Using access modifiers, you can restrict access to certain properties and functions. Public members are easily accessible outside this class. Private members are not accessible outside the class. Protected members behave like private members with one exception: Any class derived from this class can access protected members.
Accessors
Accessors in a class are getters and setters (demonstrated below by “get” and “set ” instructions) that allow us to manipulate internal members of an object. Here’s an example of how to create and use getters and setters in a class.
In the above examples we demonstrated how to create classes and define constructors, properties and functions. These members are called instance members because they are accessible only when an instance of the class is created. In the above screenshot, obj is an instance of class Products; we make use of that instance to access internal members.
Static Members
TypeScript also provides support for static members; any member that is declared as static can be directly accessed using the class itself. There’s no need to create an instance (like obj in the above example) to access static members. Let’s take a look at a quick example.
If we look at the transpiled version of the above code, you may notice that the public function getProductType is accessible through the prototype instance whereas our static method is directly accessible on the Products class.
Does using static members rather than instance members make any difference? Well, the answer is it depends. Performance-wise, the static way would probably be a little faster since it would not depend on the prototype chain; however, that performance improvement is negligible.
The choice between static and instance members depends on what you are trying to achieve. If a function or any other member doesn't require dependence on the instance member, it’s better to have it at class level by making it static.
Abstract Classes
Abstract classes are like any other normal class in TypeScript. They can have members that may or may not have any implementation. To mark a class or its members as abstract, use the keyword abstract. The major difference between abstract and non-abstract classes is that TypeScript does not allow you to create an instance of an abstract class and hence they’re typically used as base classes for other classes. In the below screenshot you can see that when we try to create an instance of the abstract class Discount, TypeScript produces an error.
An abstract class may contain both abstract and non-abstract members. Abstract members should only be defined without implementation details, but non-abstract members can have them.
Interfaces
The concept of interfaces originated in the Java and C# worlds. Interfaces are like contracts that define what is inside an object. Any class that implements an interface must conform to the contract and provide an implementation. TypeScript immediately shows an error if the class fails to conform to the contract.
Creating an interface in TypeScript is extremely simple. A very basic example of TypeScript’s interface is shown below.
interface IProducts {
id: number;
name: string;
code: string;
price: number;
productType: number;
generateDiscountCode(code: number): string;
}
In the above example, we have created an interface with some properties and a function called generateCode() that accepts a number as a parameter and returns a number. Any class that implements this interface must provide an implementation for all the interface members. Here’s an example of our ElectronicProducts class that implements the IProducts interface but fails to include productType property.
class Electronics implements IProducts {
id: number;
name: string;
code: string;
price: number;
productType: number;
generateDiscountCode(code: number): string {
let discountCode = `CODE${code}`;
return discountCode;
}
}
In the code editor (see screen shot below), TypeScript immediately shows an error since the property productType is not implemented by the class. This example shows us that the implementer must always implement all the members of the interface; if not, an error is produced at compile time.
The concept of Interfaces is internal to TypeScript. When the above code is transpiled to JavaScript, the entire interface and class-related code is gone, and what remains is JavaScript’s prototype code. Here’s a screen shot of the generated JavaScript file.
Interfaces Readonly and Optional Members
Following our above example, there are some situations where a discount code is not required for all types of products and there might not be a need for the product code to be changed externally. In such scenarios, you would mark the code property as readonly and make the generateDiscountCode an optional function, as in the below example.
interface IProducts {
id: number;
name: string;
readonly code: string; //Readonly property
price: number;
productType: number;
generateDiscountCode?(code: number): string; // Optional function
}
Notice the addition of the keyword readonly before the property code. To make the function an optional one, the question mark sign was added after the function name. If any class tries to implement this interface, it is not mandatory for it to have an implementation for generateDiscountCode function. Similarly, assigning a value to the code property produces an error. Here are two examples of classes implementing this interface.
class Electronics implements IProducts {
id: number;
name: string;
code: string;
price: number;
productType: number;
constructor() {
this.code = "Some unique code for every product";
}
generateDiscountCode(code: number): string {
let discountCode = `CODE${code}`;
return discountCode;
}
}
class Furnitures implements IProducts {
id: number;
name: string;
code: string;
price: number;
productType: number;
constructor() {
this.code = "Some unique code for every product";
}
}
In the second example above, the Furnitures class does not have an implementation for the generateDiscountCode() function, but TypeScript code will still work. Similarly, if you try to create an instance of any of these two classes and assign something to code property, as in the example below, TypeScript immediately produces an error.
Extending Interfaces
In this section, we’ll continue using the same IProducts interface, but for demonstration purposes we will remove the generateDiscountCode() function. Instead, we’ll create two more interfaces that provide some discount to a specific set of users. Here’s what the code looks like.
interface ISpecialDiscounts {
generateDiscountCodeForVIPUsers(userCode: string): string;
}
interface IBasicDiscounts {
generateDiscountCodeForBasicUsers(userCode: string): string;
}
interface IProducts extends ISpecialDiscounts, IBasicDiscounts {
id: number;
name: string;
readonly code: string;
price: number;
productType: number;
}
Notice how the IProducts interface extends both the ISpecialDiscounts and IBasicDiscounts interfaces using the extends keyword. Any class that now implements the IProducts interface must have an implementation for the functions in the discount-related interfaces (because they are not marked as optional). Here’s what the implementation of this interface would look like.
class Electronics implements IProducts {
id: number;
name: string;
code: string;
price: number;
productType: number;
constructor() {
this.code = "Some unique code for every product";
}
generateDiscountCodeForVIPUsers(userCode: string): string {
return `SPCODE${userCode}`;
}
generateDiscountCodeForBasicUsers(userCode: string): string {
return `BSCODE${userCode}`;
}
}
In the next TypeScript tutorial, you will learn about generics, namespaces, separation of concerns and more.
Chander Dhall is chairman of the Developer Platform track at IT/Dev Connections, which takes place in Dallas Oct. 15-18.
About the Author
You May Also Like