Everything You Need to Know About Advanced Typescript Concepts

Many developers who use JavaScript are familiar with the pains of debugging. When you are in charge of executing a program, you must look for new bugs and then do it again as needed. And you end up finally solving your problem after hours of troubleshooting. This is a common issue with non-compiling programming languages like JavaScript.

Microsoft invented TypeScript to address the inadequacies of JavaScript. More and more developers are expected to know Advanced TypeScript as larger organizations discover the benefits of incorporating it into their technological stack. This tutorial will discuss Advanced TypeScript Concepts that you need to know.

Learn the Ins & Outs of Software Development

Caltech Coding BootcampExplore Program
Learn the Ins & Outs of Software Development

Type Assertions

A type assertion is similar to a typecast in other languages, but it does not require additional data verification or restructuring. It does not affect runtime and is only used by the compiler. TypeScript expects you, the programmer, to complete any necessary specific checks.

There are two types of type assertions.

One is the as-syntax:

let someValue: unknown = "this is a string";

let strLength: number = (someValue as string).length;

The other version is the “angle-bracket” syntax:

let someValue: unknown = "this is a string";

let strLength: number = (<string>someValue).length;

Both samples are identical. Choosing one over the other is primarily a matter of preference; however, only-style assertions are allowed when combining TypeScript with JSX.

Type Aliases 

Type aliases give a type a new name. Type aliases are similar to interfaces. They can be used to name primitives, unions, tuples, and any other kinds that you'd have to define by hand otherwise.

Aliasing doesn't create a new type; instead, it gives it a new name. Aliasing a primitive isn't very useful; however, it can be used for documentation purposes.

Type aliases, like interfaces, can be general; all you have to do is add type parameters and utilize them on the right side of the alias declaration.

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

In Operator 

The in operator serves as a type narrowing expression. The "true" branch narrows to types that have an optional or required property n, and the "false" branch narrows to types that have an optional or missing property n for a n in x expression, where a string literal or string literal type is n and x is a union type.

function move(pet: Fish | Bird) {

 if ("swim" in pet) {

   return pet.swim();

 }

 return pet.fly();

}

Nullable Types

Null and undefined are two special types in TypeScript: null and undefined values, respectively. They're not particularly useful, much like a void. Null and undefined are subclasses of all other types by default. That means you can give things like number null and undefined values.

However, when the strict null checks flag is set, null and undefined can only be assigned to unknown, any, and their respective types (the one exception being that undefined is also assignable to void). This helps you avoid a lot of frequent errors. You can use the union type string | null | undefined if you want to send in either a string, null, or undefined.

let u: undefined = undefined;

let n: null = null;

Learn From The Best Mentors in the Industry!

Automation Testing Masters ProgramExplore Program
Learn From The Best Mentors in the Industry!

Index Types

You can get the compiler to check code that utilizes dynamic property names using index types. A typical JavaScript pattern, for example, is to select a subset of an object's properties:

You may build and use this function in TypeScript, using the index type query and indexed access operators.

function pluck<T, K extends keyof T>(o: T, propertyNames: K[]): T[K][] {

 return propertyNames.map((n) => o[n]);

}

Key of T, the index type query operator. For any type T, the key of T is the union of known, public property names of T.

T[K], the indexed access operator, is the second operator. The type syntax reflects the expression syntax in this case.

T[K] can, like index-type queries, be used in a generic environment, which is where its true potential is seen. All you have to do now is make sure the type variable K extends the key of T.

In getProperty, o: T and propertyName: K means o[propertyName]: T[K]. The compiler will initiate the actual key type after you return the T[K] result; therefore, the return type of getProperty will change depending on which property you request.

Mapped Types

Taking an existing type and making each of its properties optional is a typical undertaking.

Because this happens frequently enough in JavaScript, where TypeScript has a mapped types feature that allows you to define new types based on existing ones. The new type turns each property in the old type in the same way into a mapped type. You can, for example, make all properties optional or of the read-only type. 

It's important to note that this syntax refers to a type rather than a member. You can use an intersection type to add more members:

Take a look at a simple mapped type and its parts:

type Keys = "optionA" | "optionB";

type Flags = { [K in Keys]: boolean };

The syntax is similar to index signatures with a for in the middle. There are three sections in total:

  • The type variable K is assigned to each property one by one.
  • The literal union of strings The names of the properties to iterate over are stored in keys.
  • The property's type as a result.

Conditional Types

Based on a condition given as a type relationship test, a conditional type chooses one of two alternative types:

T extends U ? X : Y

When T can be assigned to U, the type is X, and when it can't, the type is Y.

Because the condition depends on one or more type variables, a conditional type T extends U? X: Y and is either resolved to X or Y or delayed. Whether to resolve to X or Y, or defer, when T or U contains type variables is determined by whether the type system has enough information to conclude that T is always assignable to U.

Distributive conditional types are conditional types in which the checked type is a bare type parameter. During instantiation, distributive conditional types are automatically distributed over union types.

For example, an instantiation of T extends U ? X: Y with the type argument A | B | C for T is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).

Here's How to Land a Top Software Developer Job

Full Stack Developer - MERN StackExplore Program
Here's How to Land a Top Software Developer Job

Supporting Library From Node Modules

TypeScript includes a series of declaration files to guarantee that TypeScript and JavaScript support works well right out of the box (.d.ts files). The various APIs in the JavaScript language and the standard browser DOM APIs are represented in these declaration files. While there are some fair defaults based on your target, you can configure the lib setting in the tsconfig.json to specify which declaration files your program uses.

However, there are two drawbacks to using these declaration files with TypeScript:

Since you upgrade TypeScript, you must also deal with changes to TypeScript's built-in declaration files, which can be difficult when the DOM APIs change so regularly.

Customizing these files to meet your needs and the demands of your project's dependencies is difficult. 

In TypeScript 4.5, a feature similar to @types/ support allows you to override a specific built-in lib. TypeScript will check for a scoped @typescript/lib-* package in node modules when selecting which lib files to include.

After that, you can use your package manager to install a specific package to take over for a particular library.

{

 "dependencies": {

   "@typescript/lib-dom": "npm:@types/web"

 }

}

Then, starting with TypeScript 4.5, you may update TypeScript, and the lock file in your dependency management will ensure that it utilizes the same version of the DOM types. As a result, you'll be able to update the types on your schedule.

The Awaited Type and Promise Improvements

advance_TS_Promisetype.

The Awaited type is a new utility type introduced in TypeScript 4.5. This type is intended to represent activities such as the await in async functions and the. Then () method on Promises - notably, the way they recursively unwrap Promises.

Existing APIs, such as JavaScript built-ins like Promise. all, Promise.race, and others can benefit from the Awaited type. Some of Promise.all's inference concerns are provided as a foundation for Awaited.

Promise.all combine certain traits with Awaited to produce far superior inference results.

Tail-Recursion Elimination on Conditional Types

When TypeScript identifies potentially infinite recursion or any type of expansions that take a long time and damage your editor experience, it must often gracefully fail. As a result, TypeScript includes heuristics to ensure that it doesn't run off the tracks while deconstructing an indefinitely deep type or working with types that provide many intermediate results.

type TrimLeft<T extends string> =

   T extends ` ${infer Rest}` ? TrimLeft<Rest> : T;

// Test = "hello" | "world"

type Test = TrimLeft<"   hello" | " world">;

The TrimLeft type, for example, removes spaces from the beginning of a string-like type. When provided a string type with a space at the beginning, TrimLeft returns the remainder to the user.

This type is handy, but it will throw an error if a string contains more than 50 leading spaces.

This is problematic because it frequently used these types in modeling operations on strings, such as parsers for URL routers. To make matters worse, a more useful type usually generates more type instantiations, resulting in additional input length restrictions.

TrimLeft, on the other hand, is written in such a way that it is tail-recursive on one branch. When it calls itself again, it returns the result instantly and does nothing with it. Because these types don't require any intermediate outcomes, you can construct them more rapidly without activating many of TypeScript's built-in type recursion heuristics.

As a result, TypeScript 4.5 removes some tail-recursion from conditional types. TypeScript can prevent intermediary instantiations as long as one branch of a conditional type is merely another conditional type. There are still heuristics in place to keep these types on track.

Learn the Ins & Outs of Software Development

Caltech Coding BootcampExplore Program
Learn the Ins & Outs of Software Development

Assert Signatures

The assert signatures feature allows you to construct functions that operate as type guards and side effects. Instead of returning their boolean result explicitly.

function assertString(input) {

    if (typeof input === 'string') return;

    else throw new Error('Input must be a string!');

}

function doSomething(input) {

    assertString(input);

}

doSomething('abc'); // All good

doSomething(123); // Throws an error

After assertString, TypeScript has no way of knowing if you've guaranteed the type of input. To prevent this, most people just make the parameter input: string, which is fine, but it also moves the type checking problem somewhere, and in circumstances where you just want to fail hard, having this option is beneficial.

function assertString(input: any): asserts input is string { // <-- the magic

    if (typeof input === 'string') return;

    else throw new Error('Input must be a string!');

}

function doSomething(input: string | number) {

    assertString(input);

If this function ever returns, TypeScript can filter the type of input to string, exactly like it would if inside an if block with a type guard. To make this safe, your assert function must either give an error or not return at all if the assertion isn't true.

Recursive Type Aliases

The ability to "recursively" reference type aliases has always been limited because each type of alias must be capable of substituting itself for whatever it aliases. Because this isn't always possible, the compiler rejects some recursive aliases.

Interfaces can be recursive, but their expressiveness is limited, and type aliases cannot. That involves combining the two: creating a type alias and extracting the type's recursive portions into interfaces. It's effective.

type ValueOrArray<T> = T | ArrayOfValueOrArray<T>;

interface ArrayOfValueOrArray<T> extends Array<ValueOrArray<T>> {}

By establishing an interface, users may write practically the same code.

TypeScript has no trouble working with interfaces (and other object types) because they introduce a level of indirection, and their whole structure does not need to be eagerly built up.

However, many found the workaround of introducing the interface to be inconvenient. And there was nothing wrong with the old version of ValueOrArray that used Array directly in concept. TypeScript could express them appropriately if the compiler was a little lazier and just calculated the type arguments to Array when needed.

Next Steps

Hope that this advanced TypeScript tutorial has provided you with a fundamental understanding of its concepts. Course certification will benefit you if you study these programming languages and work as a developer or programmer. Learn typescript by enrolling in the PGP Full Stack Web Developer program.

Please send us a message if you have any questions or problems about this "Advanced TypeScript" tutorial. Leave your questions and feedback in the comments section below. A member of our team will answer them for you as soon as possible!

About the Author

Kartik MenonKartik Menon

Kartik is an experienced content strategist and an accomplished technology marketing specialist passionate about designing engaging user experiences with integrated marketing and communication solutions.

View More
  • Disclaimer
  • PMP, PMI, PMBOK, CAPM, PgMP, PfMP, ACP, PBA, RMP, SP, and OPM3 are registered marks of the Project Management Institute, Inc.