8 Advanced features of Typescript: A deeper dive into the language

Advanced features of Typescript
Jan 23 2023 · 6 min read

Typescript is a strict syntactical superset of JavaScript developed by Microsoft and adds optional static typing to the language. TypeScript is used for building large-scale, maintainable JavaScript applications.

Unlike Javascript, Typescript can identify errors or mistakes at the early stage of the development process (at compile time). It can save time by reducing efforts to change code when issues arise after running the applications.

Today, we will discuss advanced features of Typescript like decorators, generics, enums, and some others, which can be useful in your application to make it more scalable.


Sponsored

Your daily habits shape your future. Try out justly and make your future more advanced.


1. Decorators

Decorators are a way to add additional functionality to classes, properties, methods, and accessors. It executes code before an actual class instance has been created.

They are a powerful feature that allows for a more expressive and elegant way to write code.

We can enable support for decorators in javascript by command line or from tsconfig.json as below:

Command Line

$tsc --target ES5 --experimentalDecorators

tsconfig.json

{  
    "compilerOptions": {  
        "target": "ES5",  
        "experimentalDecorators": true  
    }  
}

Example,

// Define the decorators
function logger(target: any) {
    console.log(`Creating an instance of ${target.name}`);
}

function logMethod(target: any, key: string, descriptor: PropertyDescriptor) {
    console.log(`Calling ${key} method`);
}

function logProperty(target: any, key: string) {
    console.log(`Accessing ${key} property`);
}

// Use the decorator on a class and its properties
@logger
class TestClass {
    
    @logMethod
    testMethod() {
        console.log("testMethod called");
    }

    @logProperty
    testProp:string;
}

// It will print "Creating an instance of TestClass"
const testClass = new TestClass(); 

// It will print "Calling testMethod method"
testClass.testMethod();

// It will print "Accessing testProp property"
console.log(testClass.testProp);

In the above code, We have defined decorators (logger, logMethod, logProperty) and used them before the class, method, and property definitions. They will execute before the actual code will be executed.


2. Type Inference

Typescript is a typed language. However, it is not necessary to define types on the variable declaration. Typescript can infer the type based on its declaration.

let a = 1; // let a: number = 1

The type of the a variable is inferred to be number. This kind of inference takes place when initializing variables and members, setting parameter default values, and determining function return types.

Best common types

If the object is initialized with multiple types, TypeScript looks for the most common type to infer the type of the object.

let a = [0, 1, null]; // let a: (number | null)[]

The type of a variable is inferred from its best common type, that is number | null

Learn more about type inference here.


3. Generics

For building large and scalable software systems, we sometimes have to reuse the components.

Generics allow you to create reusable components that can work with multiple types of data.

We can use any for allowing multiple types to function. But we lose the information about the type when the function returns in that case.

function identity(arg: any): any {
  return arg;
}

Below is an example of creating generics in typescript,

function identity<T>(arg: T): T {
    return arg;
}

Here T is a type of variable. In the context of the above code, we can say that If T is a string, then arg type and function’s return type is also string.

The below snippet shows how a generic function can be called,

let message = identity<string>("Hello world!!"); // message = Hello world!!

4. Enums

Same as the other languages, Typescript also provides an Enum facility. Enums allow us to define a set of named constants. Typescript has both numeric and string type Enums.

  • Numeric enums are auto-incremented, means if the first value is defined, it will automatically assign its following value to other enums.
  • In a string enum, each member has to be constant-initialized with a string literal, or with another string enum member.

Example,

// numeric 
// Up = 1, Down = 2, Left = 3, Right = 4
enum Direction { Up, Down, Left, Right } 

// or
// Up = 3, Down = 4 , Left = 5, Right = 6
enum Direction {
  Up = 3,
  Down,
  Left,
  Right,
}  

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

5. Namespaces

Namespaces provide a way to structure the codebase logically, making it easier for others to understand and use. Namespaces are similar to modules in other programming languages and are used to prevent naming collisions between different parts of the code.

The basic syntax for creating a namespace is as below,

namespace Calculations {
    export function sum(x: number, y: number) {
        return x + y;
    }
    export function product(x: number, y: number) {
        return x * y;
    }
}

How to access those functions?

console.log(Calculations.sum(1,2)) // 3
console.log(Calculations.product(4,5)) // 20

Namespaces can also be split across multiple files. You can use the “/// <reference path=’’/>” to refer to another file that contains the same namespace.

// calculations.ts
namespace Calculations {
    export function sum(x: number, y: number) {
        return x + y;
    }
}

// main.ts
/// <reference path="calculations.ts" />
console.log(Calculations.sum(1,2)) // 3

We can also use one namespace inside another.

namespace Calculations {
    export function sum(x: number, y: number) {
        return x + y;
    }
    export namespace Circle {
        export function circumference(d: number) {
            return 2 * Math.PI * d;
        }
    }
}
console.log(Calculations.Circle.circumference(10)) // 62.83185307179586

6. Tuples

A tuple is an ordered, fixed-size collection of elements, where each element can have a different type.

Tuples are similar to arrays, but with a key difference: the types of the elements and the number of elements in a tuple are known at compile time.

Tuples can be useful in situations where you need to pass a fixed number of elements with different types to a function or return multiple values from a function.

Example,

let person: [string, number, boolean] = ['Sem', 25, true];

If we assign different values to the tuple elements, it will throw an error like below,

// define our tuple
let person: [string, number, boolean];

// throws an error as initialized with different types
person = [true, 'Sem', 25];

Tuple destructuring is used to unpack the elements of a tuple into separate variables.

let [name, age, isStudent] = person;
console.log(name); // 'Sem'
console.log(age); // 25
console.log(isStudent); // true

7. Conditional Types

There may be a situation when we have to make a decision about type of output based on its input type .

In that case, the most advanced feature of typescript conditional types will be useful. It allows us to express a type that depends on a condition.

Example,

type UnwrapArray<T> = T extends (infer U)[] ? U : never;

Above is a conditional type that returns the type of the input (U) if it’s an array, otherwise, it returns “never”.

You can also use the “extends” keyword to check if a type is a subtype of another type.

type IsString<T> = T extends string ? true : false;
let a: IsString<string> = true;
let b: IsString<number> = false;

8. Advanced Types

TypeScript offers several advanced types that can be used to express more complex type relationships and constraints.

  • Intersection types allow you to combine multiple types into a single type. by using the “&” operator between the two types.
type A = { a: number };
type B = { b: string };
type AB = A & B;
let ab: AB = { a: 1, b: "hello" };
  • Union types allow you to specify that a value can be of one of several types using the “|” operator between two types.
let value: number | string = "hello";
value = 20;
  • Type Aliases allow you to give a type a different name. For example:
type Name = string;
let name: Name = "John";
  • The keyof operator is used to extract the keys of an object.
// returns "id" | "name" | "description"
type Keys = keyof { id: 1, name: 2, description: 3 }; 
  • The in operator is used to check if a property exists in a type
let obj = { a: 1, b: "hello" };
let propExist: 'a' in obj // true

You can explore more advanced types from the Advanced typescript types cheat sheet.


Conclusion

TypeScript is an object-oriented programming language. It provides many of the features of traditional object-oriented languages such as classes, interfaces, inheritance, and polymorphism.

You can explore and use those features to make your software application more scalable and well-defined.

That’s it for today, Keep exploring for the best.


Related articles


sumita-k image
Sumita Kevat
Sumita is an experienced software developer with 5+ years in web development. Proficient in front-end and back-end technologies for creating scalable and efficient web applications. Passionate about staying current with emerging technologies to deliver.


sumita-k image
Sumita Kevat
Sumita is an experienced software developer with 5+ years in web development. Proficient in front-end and back-end technologies for creating scalable and efficient web applications. Passionate about staying current with emerging technologies to deliver.

Let's Work Together

Not sure where to start? We also offer code and architecture reviews, strategic planning, and more.

cta-image
Get Free Consultation
footer
Subscribe Here!
Follow us on
2024 Canopas Software LLP. All rights reserved.