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:
var brand: string = 'Chevrolet';
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:
var brand: string = 'Chevrolet';
var message: string = `Today it's a happy day! I just bought a new ${brand} car`;
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:
function test() {
var x;
}
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:
var i = 3;
for (var i = 0; i < 10; i++) {
}
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:
let i = 3;
for (let i = 0; i < 10; i++) {
}
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:
const PI = 3.14;
PI = 3;
If we try to run it with TypeScript, the compiler will throw the following error message:
Cannot assign to 'PI' because it is a constant
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:
const obj = {
a: 3
};
obj.a = 4;
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
:
const age: number = 7;
const height: number = 5.6;
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
:
const isZeroGreaterThanOne: boolean = 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:
const brands: string[] = ['Chevrolet', 'Ford', 'General Motors'];
const ages: number[] = [8, 5, 12, 3, 1];
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:
let distance: any;
distance = '1000km';
distance = 1000;
const distances: any[] = ['1000km', 1000];
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:
type Animal = 'Cheetah' | 'Lion';
It is essentially a type with a finite number of allowed values. Let’s create a variable of this type:
const animal: Animal = 'Cheetah';
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:
const animal: Animal = 'Turtle';
The preceding code will result in the following compiler error:
Type '"Turtle"' is not assignable to type 'Animal'
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:
enum Brands { Chevrolet, Cadillac, Ford, Buick, Chrysler, Dodge };
const myCar: Brands = Brands.Cadillac;
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:
enum BrandsReduced { Tesla = 1, GMC, Jeep };
const myTruck: BrandsReduced = BrandsReduced.GMC;
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:
enum StackingIndex {
None = 0,
Dropdown = 1000,
Overlay = 2000,
Modal = 3000
};
const mySelectBoxStacking: StackingIndex = StackingIndex.Dropdown;
One last point worth mentioning is the possibility to look up a member mapped to a given numeric value:
enum Brands { Chevrolet, Cadillac, Ford, Buick, Chrysler, Dodge };
const myCarBrandName: string = Brands[1];
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:
function test(): void {
const a = 0;
}
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.