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?
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.
Just beginning to learn TypeScript? Check out our What is TypeScript? Edpresso shot to get a more in-depth introduction to 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.
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.
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 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.
Prep for your next TS interview the right way. Educative’s text-based courses are easy to skim and feature live coding environments to help you learn in half the time.
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.
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);
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` typesend(arg);}
To fix the error, you can annotate the function argument.
function print(arg: number) { // Error : someArg has an implicit `any` typesend(arg);}
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` typesend(arg);}
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 errorconst 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)}
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.
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; // Okaynum = undefined; // Okay
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!
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'
.
class Dog{age: numberbreed: stringconstructor(age: number, breed: string){this.age = agethis.breed = string}getRelativeAge(): number{return this.age * 7}}let Luna = new Dog(2, 'Labrador')
This syntax is equivalent to using function-objects in JavaScript ES5.
function Dog(age, breed){this.age = agethis.breed = breed}Dog.prototype.getRelativeAge = function() {return this.age * 7}var Spot = new Dog(2, 'Labrador')
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: numberbreed: stringconstructor(age: number, breed: string){this.age = agethis.breed = breed}makeSound_(sound: string): void{console.log(sound)console.log(sound)console.log(sound)}}
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 constructorthis.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')}}
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;
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
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}
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;}
One of the most integral aspects of TypeScript is creating custom types from existing generic types.
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) => { /* ... */ };
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
.
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;
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];// OkaynameNumber = ['Ben', 12345];// ErrornameNumber = ['Ben', '12345'];
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
Other type guards
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 TypeScript for Front-End Developers 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.
Free Resources