Introduction to TypeScript
As we learned in the previous chapter, where we built our very first Angular application, the code of an Angular project is written in TypeScript. Writing in TypeScript and leveraging its static typing gives us a remarkable advantage over other scripting languages. This chapter is not a thorough overview of the TypeScript language. Instead, we’ll focus on the core elements and study them in detail on our journey through Angular. The good news is that TypeScript is not all that complex, and we will manage to cover most of its relevant parts.
In this chapter, we’re going to cover the following main topics:
- The history of TypeScript
- Types
- Functions, lambdas, and execution flow
- Common TypeScript features
- Decorators
- Advanced types
- Modules
We will first investigate the background of TypeScript and the rationale behind its creation. We will also learn what tools and online resources are available to practice with TypeScript. We will emphasize the typing system, which is the main advantage of TypeScript, and learn how we can use it to create some basic types. We will expand our typing knowledge by learning how to use classes, interfaces, and advanced types in the Angular context. At the end of the chapter, we will explore how to organize the structure of an application by combining the typing system with modules.
The history of TypeScript
Transforming small web applications into thick monolithic clients was impossible due to the limitations of earlier JavaScript versions, such as the ECMAScript 5 specification. In a nutshell, large-scale JavaScript applications suffered from serious maintainability and scalability problems as soon as they grew in size and complexity. This issue became more relevant as new libraries and modules required seamless integration into our applications. The lack of proper mechanisms for interoperability led to cumbersome solutions that never seemed to fit the bill.
As a response to these concerns, ECMAScript 6 (also known as ES6 or ES2015) promised to solve these issues by introducing better module loading functionalities, an improved language architecture for better scope handling, and a wide variety of syntactic sugar to better manage types and objects. Class-based programming introduced an opportunity to embrace a more Object-Oriented Programming (OOP) approach when building large-scale applications.
Microsoft took on this challenge and spent nearly two years building a superset of the language, combining the conventions of ES6 and borrowing some proposals from the next specification version. The idea was to launch something that would help build enterprise applications with a lower error footprint using static type checking, better tooling, and code analysis. After two years of development led by Anders Hejlsberg, lead architect of C# and creator of Delphi and Turbo Pascal, TypeScript 0.8 was finally introduced in 2012 and reached version 1.0 2 years later. It was not only running ahead of ES6 but also implemented the same features and provided a stable environment for building large-scale applications. It introduced, among other features, optional static typing through type annotations, thereby ensuring type checking at compile time and catching errors early in the development process. Its support for declaration files also enabled developers to describe the interface of their modules so that other developers could better integrate them into their code workflow and tooling.
The benefits of TypeScript
As a superset of JavaScript, one of the main advantages of embracing TypeScript in your next project is the low entry barrier. If you know JavaScript, you are pretty much all set since all the additional features in TypeScript are optional. You can pick and introduce any of them to achieve your goal. Overall, there is a long list of solid arguments for advocating TypeScript in your next project, and all of them apply to Angular as well.
Here is a short rundown, to name a few:
- Annotating your code with types ensures a consistent integration of your different code units and improves code readability and comprehension.
- The built-in type-checker analyzes your code at compile time and helps you prevent errors before executing your code.
- The use of types ensures consistency across your application. Combined with the previous two, the overall code error footprint gets minimized in the long run.
- TypeScript extends classes with long-time demanded features such as class fields, private members, and enumerations.
- Decorators allow you to extend your classes and implementations in unique ways.
- Interfaces ensure a smooth and seamless integration of your libraries in other systems and code bases.
- TypeScript support across different IDEs is terrific, and you can benefit from features such as highlighting code, real-time type checking, and automatic compilation at no cost.
- The syntax is familiar to developers coming from other OOP-based backgrounds such as Java, C#, and C++.
Introducing TypeScript resources
Let’s have a look at where we can get further support to learn and test-drive our new knowledge of TypeScript.
In this book, we will be using TypeScript 4.8 as it is supported by Angular 15.
The official website
Our first stop is the official website of the language at https://www.typescriptlang.org.
It contains extensive language documentation and a playground that gives us access to a quick tutorial to get up to speed with the language in no time. It includes some ready-made code examples that cover some of the most common traits of the language. We encourage you to leverage this tool to test the code examples we cover throughout this chapter.
The official wiki documentation
The code for TypeScript is fully open-sourced at GitHub, and the Microsoft team has put reasonable effort into documenting the different facets of the code in the wiki available on the repository site. We encourage you to take a look at it any time you have a question or if you want to dive deeper into any of the language features or form aspects of its syntax. The wiki is located at https://github.com/Microsoft/TypeScript/wiki.
In the following section, we will introduce the typing system of TypeScript. We will explore the most basic types of the TypeScript language. We will also learn how to benefit from the typing system and create custom and dynamic types to enhance our applications further.
Types
Working with TypeScript or any other coding language means working with data, and such data can represent different sorts of content that are called types. Types are used to represent the fact that such data can be a text string, an integer value, or an array of these value types, among others. You may have already met types in JavaScript since we have always worked implicitly with them. This also means that any given variable could assume (or return, in the case of functions) any value. Sometimes, this leads to errors and exceptions in our code because of type collisions between what our code returned and what we expected it to return type-wise. We can enforce this flexibility using a specific type called any
, as we will see later in this chapter. However, statically typing our variables gives our IDE and us a good picture of what kind of data we are supposed to find in each instance of code. It becomes an invaluable way to help debug our applications at compile time before the code is executed.
String
One of the most widely used primitive types is the string
, which populates a variable with a piece of text:
Check out the type definition next to the variable name, separated by a colon. It is the way to annotate types in TypeScript.
We can use single or double quotes for the value of a string variable. Feel free to choose either and stick with it within your team. We can define multiline text strings with support for text interpolation using placeholder variables and backticks:
In the preceding snippet, any variables that we may use inside the multiline text must be surrounded by the curly braces of the placeholder ${}
.
Declaring variables
TypeScript, as a superset of JavaScript, supports expressive declaration nouns such as let
, which denotes that the scope of the variable is the nearest enclosing block (either a function, for
loop, or any other enclosing statement). On the other hand, const
indicates that the value of the declared variable cannot be changed once it’s set.
The let keyword
Traditionally, developers have been using the keyword var
to declare objects, variables, and other artifacts, but this is discouraged when you start using ES6 or TypeScript. The reason is that ES5 only has a function scope; that is, a variable is unique within the context of a function:
There can be no other variable declared as x
in this function. If you do declare one, then you effectively redefine it. However, there are cases in which scoping is not applied, such as in loops. For example, in Java, you would write the following and ensure that a variable will never leak outside of the loop:
In the preceding snippet, the i
variable outside the loop will not affect the i
variable inside it because they have a different scope. To overcome this limitation, ES6 introduced the let
keyword:
So, remember, no more var
; use the let
keyword wherever possible.
The const keyword
The const
keyword is a way to indicate that a variable should never change. As a code base grows, changes may happen by mistake, which can be costly. The const
keyword can prevent these types of mistakes through compile-time support. Consider the following code snippet:
If we try to run it with TypeScript, the compiler will throw the following error message:
The preceding error will come up only at the top level. You need to be aware of this if you declare objects as constants, like so:
If we declare the obj
variable as a constant, it does not prevent the entire object from being edited but rather its reference. So, the preceding code is valid. If we try to change the reference of the variable such as obj = {}
, it is not allowed, and we get the same compiler error.
Prefer to use the const
keyword when you are sure that the properties of an object will not change during its lifetime. It prevents the object from accidentally changing and enforces data immutability, a hot topic in Angular applications.
Number
The number
type is probably the other most widespread primitive data type, along with string
and boolean
:
It defines a floating-point number and hexadecimal, decimal, binary, and octal literals.
Boolean
The boolean
type defines a variable that can have a value of either true
or false
:
The result of the variable represents the fulfillment of a boolean
condition.
Array
The array type defines a list of items that contain certain types only. Handling exceptions that arise from errors such as assigning wrong member types in a list can now be easily avoided with this type.
The syntax requires the postfix []
in the type annotation, as follows:
If we try to add a new item to the ages
array with a type other than a number, the runtime type-checker will complain, making sure our typed members remain consistent and that our code is error-free.
Dynamic typing with no type
Sometimes, it is hard to infer the data type from the information we have at any given point, especially when we are porting legacy code to TypeScript or integrating loosely typed third-party libraries and modules. TypeScript supplies us with a convenient type for these cases. The any
type is compatible with all the other existing types, so we can type any data value with it and assign any value to it later:
However, this great power comes with great responsibility. If we bypass the convenience of static type checking, we are opening the door to type errors when piping data through our modules. It is up to us to ensure type safety throughout our application.
Custom types
In TypeScript, you can come up with your own type if you need to by using the type
keyword in the following way:
It is essentially a type with a finite number of allowed values. Let’s create a variable of this type:
The preceding code is perfectly valid as Cheetah
is one of the allowed values and works as intended. The interesting part happens when we give our variable a value it does not expect:
The preceding code will result in the following compiler error:
Enum
The enum
type is a set of unique numeric values that we can represent by assigning user-friendly names to each one. Its use goes beyond assigning an alias to a number. We can use it to list the variations that a specific type can assume in a convenient and recognizable way. It begins numbering members, starting at 0 unless explicit numeric values are assigned to them:
In the preceding code, if we inspect the variable myCar
, we will see that it returns the value 1, which is the index of Cadillac
. As we mentioned already, we can also assign custom numeric values like the following:
In the preceding code, if we inspect the variable myTruck
, we will see that it returns the value 2 because the first enumerated value, Tesla
, was set to 1
already. We can extend value assignation to all members as long as such values are integers:
One last point worth mentioning is the possibility to look up a member mapped to a given numeric value:
In the preceding snippet, the myCarBrandName
variable will be equal to Cadillac
.
It should also be mentioned that from TypeScript 2.4 and onward, it is possible to assign string values to enums. It is a technique preferred in Angular projects because of its extended support in template files.
Void
The void
type represents the absence of a type, and its use is constrained to annotating functions that do not return an actual value:
In the preceding snippet, there is no return type in the function.
Type inference
Typing is optional since TypeScript is smart enough to infer the data types of variables and function return values out of context with a certain level of accuracy. If it is not possible, it will assign the dynamic any
type to the loosely typed data at the cost of reducing type checking to a bare minimum.
In the following section, we will embark on a new journey through TypeScript to learn more about TypeScript functions and their execution flow.