DEV Community

Cover image for Understanding Advanced Concepts in Typescript
Amanda Fawcett for Educative

Posted on • Edited on • Originally published at educative.io

Understanding Advanced Concepts in Typescript

This article was written by Aaron Xie and was originally published at Educative, Inc.

When using JavaScript, many developers know the headache of debugging. You run a program. Find a new bugs. Rinse and repeat. After hours of debugging, you’ve finally fixed your problem. This is a common problem with a programming language like JavaScript that doesn’t compile.

In an effort to solve the shortcomings of JavaScript, Microsoft created TypeScript. As larger teams realize the benefits of adopting TypeScript into their technology stack, more and more developers are required to know it.

Today, you are going to learn some advanced concepts in TypeScript so that you are your way to becoming an expert.

You will be learning:

  • What is TypeScript?

  • Advantages and limitations of TypeScript

  • Intro to Strict types

  • TypeScript and Object-oriented programming

  • Types in TypeScript

  • Other topics to learn

  • Wrapping up and resources

What is TypeScript?

Created and maintained by Microsoft, TypeScript is a superset of JavaScript, meaning that all functional JavaScript code is valid under TypeScript. The language can be understood as "JavaScript for application-scale development," with two primary focuses:

  • Provide features from future JavaScript engines to current JavaScript engines

  • Provide a type system for JavaScript

The components of TypeScript are typically the language itself, which is essentially JavaScript plus additional features and syntax, the compiler that converts the code into JavaScript, and the language service, which provides editor-like applications near the end of the compiler pipeline.

So, why use TypeScript?

  • Typing: TypeScript offers static typing, which many large teams like Microsoft and Google have found beneficial to streamline the development process.

  • Object-oriented programming: TypeScript supports object-oriented programming concepts like interfaces, inheritance, classes, and more.

  • Compilation: Unlike JavaScript, which is an interpretative language, TypeScript compiles the code for you and finds compilation errors, which make it easier to debug.

Alt Text

Installing TypeScript

Before we dive into TypeScript, make sure that you have successfully installed TypeScript. The two primary ways to get the TypeScript tools is through npm, the Node.js package manager, or by installing TypeScript's visual studio plugins.

NPM:

Install

> npm install -g typescript

Compile

> tsc helloworld.ts

If you aren't using NPM, you can download TypeScript through this link.

Advantages and limitations of TypeScript

Typing

JavaScript is a dynamically typed language, meaning that type errors are found only during runtime. This can be a significant downside for large teams working on complex projects, as finding all mistakes to the code beforehand would be significantly easier.

TypeScript offers optional static typing so that a variable can't change its types and can only accept certain values. This typing helps the TypeScript compiler find more bugs so that developers are working with less error-prone code. Type guards create more structure for the code by making it more readable, and more easily refactored.

Interested in learning about more differences between JavaScript and TypeScript? You can learn more here: What's the difference between JavaScript and TypeScript?

IDE support

Because TypeScript offers types, text editors and integrated development environments (IDE) can provide more helpful information to developers. These environments can offer auto-completion, code navigation, error flagging and more to increase the productivity of teams.

Some popular environments that support TypeScript 3:

  • Microsoft Visual Studio

  • WebStorm

  • Visual Studio Code

  • Atom

  • Eclipse

Browser compatibility

Browser compatibility is one of the powerful features that TypeScript offers. The TypeScript compiler transforms your code to make it compatible with all the modern browsers. This compatibility is because the compiler is able to translate the TypeScript code into vanilla JS, which all devices, platforms, and browser support.

Though there are many advantages to using TypeScript, it's not a perfect solution. One downside to improving your code readability is that you have to write more code, which can potentially increase your development time. It also increases the size of your TypeScript files compared to using vanilla JavaScript.

Become a TypeScript developer.

Educative's Learn TypeScript course allows complete beginners to learn the primary concepts to become a full-fledged TypeScript developer.

Learn TypeScript: The Complete Course for Beginners

Already a TypeScript learner?

Tackle the advanced concepts and improve your TS skills with hands-on practice.

Advanced TypeScript MasterClass

Intro to Strict Types

Now that we have a sense of what TypeScript has to offer, let's dive into some of the more advanced concepts that make TypeScript a powerful tool.

noImplicitAny

According to the documentation, the definition of noImplicitAny is to "raise errors on expressions and declarations with any implied any type."

This means that whenever TypeScript can infer a type, you will get an error if you allow noImplicitAny. This example can be seen by passing function arguments.

function print(arg) {
    send(arg);
}

print("hello");
print(4);
Enter fullscreen mode Exit fullscreen mode

In the above code, what are valid arguments for the print function? If you don't add a type to the function argument, TypeScript will assign the argument of type any, which will turn off type checking.

For developers who prefer safety in their code, they can utilize noImplicityAny, which will notify them of any possibilities for type any in their code. Let's see what will happen with the same print function.

function print(arg) { // Error : someArg has an implicit `any` type
    send(arg);
}
Enter fullscreen mode Exit fullscreen mode

To fix the error, you can annotate the function argument.

function print(arg: number) { // Error : someArg has an implicit `any` type
    send(arg);
}
Enter fullscreen mode Exit fullscreen mode

But if you still want type any, you can explicitly mark the argument as any.

function print(arg: any) { // Error : someArg has an implicit `any` type
    send(arg);
}
Enter fullscreen mode Exit fullscreen mode

unknown

The unknown type is similar to the any type in that all types are assignable to the any and unknown type, but the distinction is that the any type is assignable to any other types, but the unknown type is un-assignable to any other type. The distinction can be a confusing concept, so let's take a look at an example.

function example1(arg: any) {
  const a: str = arg; // no error
  const b: num = arg; // no error
}

function example2(arg: unknown) {
  const a: str = arg; // 🔴 Type 'unknown' is not assignable to type 'string'.(2322)
  const b: num = arg; // 🔴 Type 'unknown' is not assignable to type 'number'.(2322)
}
Enter fullscreen mode Exit fullscreen mode

A variable arg is passed to both functions, which can have a type of string, number, or another type. No matter its type, arg is then assigned the type any and unknown.

However, unlike the any type, a variable of unknown type cannot then be assigned to another type, as seen in lines 7 and 8. The any type is bidirectional, whereas unknown is unidirectional.

The unknown type can be helpful in cases where you don't know the type of a value you are passing into a function but would like to get rid of the any cases. This increases the safety of your code, as the any type can propagate, making your codebase more prone to errors.

strictNullChecks

In TypeScript, null and undefined are assignable to every type, meaning that they are in the domain of all types.

let num: number = 123;
num = null; // Okay
num = undefined; // Okay
Enter fullscreen mode Exit fullscreen mode

Oftentimes, this can lead to unexpected errors, as you can call methods on a variable whose value is null or undefined.

interface Person {
  hello(): void;
}

const num: number = undefined;
const str: string = null;
const person: Person = null;

person.hello(); // 🔴 Runtime Error!
Enter fullscreen mode Exit fullscreen mode

In strict null checking mode, null and undefined do not automatically belong to all types, and therefore you can't use them for a type that doesn't include null or undefined. This way, you can get an error at compile time that says Object is possibly 'undefined'.

Luna is an instance-object of Dog.

class Dog
{
    age: number
    breed: string    

    constructor(age: number, breed: string) 
    {
        this.age = age
        this.breed = string
    }    

    getRelativeAge(): number
    {
        return this.age * 7
    }
}

let Luna = new Dog(2, 'Labrador')
Enter fullscreen mode Exit fullscreen mode

This syntax is equivalent to using function-objects in JavaScript ES5.

function Dog(age, breed)
{
    this.age = age
    this.breed = breed
}

Dog.prototype.getRelativeAge = function() {
    return this.age * 7
}

var Spot = new Dog(2, 'Labrador')
Enter fullscreen mode Exit fullscreen mode

Inheritance

Now that you know how to create objects, it's important to learn about inheritance in TypeScript. Inheritance allows subclasses to inherit certain attributes from its parent class.

For example, you have Animal, as a parent class.

class Animal
{
    age: number
    breed: string    

    constructor(age: number, breed: string)
    { 
        this.age = age
        this.breed = breed
    }    

    makeSound_(sound: string): void
    {
        console.log(sound)
        console.log(sound)
        console.log(sound)
    }
}
Enter fullscreen mode Exit fullscreen mode

Then, you can create a Dog subclass. You can implement basic inheritance using the super keyword, which is used as a function in the subclass to call the corresponding parent function.

class Dog extends Animal
{
    playsFetch: boolean    constructor(age: number, breed: string, playsFetch: boolean)
    {
         super(age, breed) // call parent constructor
         this.playsFetch = playsFetch
    }    makeSound(): void
    {
        super.makeSound_('woof woof')
    }    getAgeInHumanYears(): number
    {
        return this.age * 7    // super.age will throw error
    }
}
class Cat extends Animal
{
    constructor(age: number, breed: string)
    {
        super(age, breed)
    }    makeSound(): void
    {
        super.makeSound_('meow meow')
    }
}
Enter fullscreen mode Exit fullscreen mode

Interfaces

Interfaces are powerful in JavaScript (and TypeScript), because they have zero runtime impact. TypeScript allows you to declare the structure of the variables, which gives you even more power.

interface Point {
    x: number; y: number;
}
declare var test: Point;
Enter fullscreen mode Exit fullscreen mode

Interfaces in TypeScript are open-ended, so another author can build upon the existing declaration of the test variable.

interface Point {
    x: number; y: number;
}
declare var myPoint: Point;

interface Point {
    z: number;
}

var myPoint.z; // Allowed
Enter fullscreen mode Exit fullscreen mode

Classes can also implement interfaces so that they follow a pre-defined object structure by using the implements keyword.

interface Point {
    x: number; y: number;
}

class MyPoint implements Point {
    x: number; y: number; // Same as Point
}
Enter fullscreen mode Exit fullscreen mode

Because of this implements keyword, any changes in the interface will create a compilation error so that you can easily update your codebase.

interface Point {
    x: number; y: number;
    z: number; // New member
}

class MyPoint implements Point { // ERROR : missing member `z`
    x: number; y: number;
}
Enter fullscreen mode Exit fullscreen mode

Types in TypeScript

One of the most integral aspects of TypeScript is creating custom types from existing generic types.

Union type

Oftentimes, you may want your code to allow more than one data type. This need is especially true when accepting a null or undefined value. The union type can solve this problem, which is denoted by the | annotation.

const hello = (name: string | undefined) => { /* ... */ };
Enter fullscreen mode Exit fullscreen mode

In this example, the type name is defined as string | undefined, meaning that any variable of type name can either be a string or undefined.

Intersection type

An intersection type combines multiple types into one, such that the new type has the features of the combined types. You can do this through the extend keyword, as seen below.

function extend<T, U>(first: T, second: U): T & U {
  return { ...first, ...second };
}

const x = extend({ a: "hello" }, { b: 42 });

// x now has both `a` and `b`
const a = x.a;
const b = x.b;
Enter fullscreen mode Exit fullscreen mode

Tuple type

Unlike JavaScript, TypeScript offers Tuple types, which allow you to express an array with non-uniform types and a fixed number of elements. A tuple is demonstrated in the below example.

var nameNumber: [string, number];

// Okay
nameNumber = ['Ben', 12345];

// Error
nameNumber = ['Ben', '12345'];
Enter fullscreen mode Exit fullscreen mode

Other topics to learn

There's much more to learn to be a true master of TypeScript. Take a look at this list to see what lies ahead.

  • Mapped types

  • Discriminated union types

  • Decorators

  • Function types and return types

  • Functional programming in TypeScript

  • State machines

  • Generic functions

Wrapping up and Resources

Now that you have been introduced to some more advanced topics in TypeScript, it's time to begin exploring even more of the powerful TypeScript features. Check our Advanced TypeScript Masterclass to master the language and fully utilize the tools offered by TypeScript.

After finishing the course, you will feel more confident in your TypeScript skills, being able to write your own types, easily identify errors after compiling, and even improving your overall JavaScript knowledge. The topics that will be covered are strict types, generic functions, generic interfaces, composing types, common errors, and more.

Continue reading about TypeScript

Top comments (1)

Collapse
 
negue profile image
negue

Nice examples for typescript concepts :), these do help a lot, depending on how your datastructures are!!

I wanted to add an optimized way of adding properties/fields to your classes:

Instead of this:

class Dog
{
    age: number
    breed: string    

    constructor(age: number, breed: string) 
    {
        this.age = age
        this.breed = breed
    }    

    getRelativeAge(): number
    {
        return this.age * 7
    }
}

you can shorten the code/constructor and use this:

class Dog
{
    // depending if those are private or public, here public
    constructor(public age: number, public breed: string) 
    {
    }    

    getRelativeAge(): number
    {
        return this.age * 7
    }
}

that way you don't need to code these parts yourself and with that have one / more lines where you can't make potential bugs :) - so win/win 🎉