Common TypeScript features
TypeScript has some general features that don’t apply to classes, functions, or parameters but make coding more efficient and fun. The idea is that the fewer lines of code we write, the better it is. It’s not only about fewer lines but also about making things more straightforward. There are many such features in ES6 that TypeScript has also implemented. In the following sections, we’ll name a few that you will likely use in an Angular project.
Spread parameter
A spread parameter uses the same ellipsis syntax as the rest parameter but is used inside the body of a function. Let’s illustrate this with an example:
const newItem = 3;
const oldArray = [1, 2];
const newArray = [...oldArray, newItem];
In the preceding snippet, we add an item to an existing array without changing the old one. The old array still contains 1
, 2
, whereas the new array contains 1
, 2
, and 3
. The current behavior is called immutability, which means not changing the old array but rather creating a new state from it. It is a principle used in functional programming as a paradigm and for performance reasons.
We can also use a spread parameter on objects:
const oldPerson = { name: 'John' };
const newPerson = { ...oldPerson, age: 20 };
In the preceding snippet, we are creating a merge between the two objects. Like in the array example, we don’t change the previous variable, oldPerson
. Instead, the newPerson
variable takes the information from the oldPerson
variable and adds its new values to it.
Template strings
Template strings are all about making your code clearer. Consider the following:
const url = 'http://path_to_domain' +
'path_to_resource' +
'?param=' + parameter +
'¶m2=' + parameter2;
So, what’s wrong with the previous snippet? The answer is readability. It’s hard to imagine what the resulting string will look like but editing the code by mistake and producing an unwanted result is also easy. To overcome this, we can use template strings in the following way:
const url =
`${baseUrl}/${path_to_resource}?param=${parameter}¶m2={parameter2}`;
The preceding syntax is a much more condensed expression and much easier to read.
Generics
Generics are expression indicating a general code behavior that we can employ, regardless of the data type. They are often used in collections because they have similar behavior, regardless of the type. They can, however, be used on other constructs such as methods. The idea is that generics should indicate if you are about to mix types in a way that isn’t allowed:
function method<T>(arg: T): T {
return arg;
}
method<number>(1);
In the preceding example, the type of T
is not evaluated until we use the method. As you can see, its type varies, depending on how you call it. It also ensures that you are passing the correct type of data. Suppose that the preceding method is called in this way:
method<string>(1);
We specify that T
should be a string
, but we insist on passing it a value as a number. The compiler clearly states that this is not correct. You can, however, be more specific on what T
should be. You can make sure that it is an array type so that any value you pass must adhere to this:
function method<T>(arg: T[]): T[] {
console.log(arg.length);
return arg;
}
class CustomPerson extends Array {}
class Person {}
const people: Person[] = [];
const newPerson = new CustomPerson();
method<Person>(people);
method<CustomPerson>(newPerson);
In the preceding case, we decide that T
should be of Person
or CustomPerson
type and that the parameter needs to be of the array type. If we try to pass a single object, the compiler will complain:
const person = new Person();
method<Person>(person);
Alternatively, we can define that T
should adhere to an interface like this:
interface Shape {
area(): number;
}
class Square implements Shape {
area() { return 1; }
}
class Circle implements Shape {
area() { return 2; }
}
function allAreas<T extends Shape>(...args: T[]): number {
let total = 0;
args.forEach (x => {
total += x.area();
});
return total;
}
allAreas(new Square(), new Circle());
Generics are powerful to use if you have a typical behavior with many different data types. You probably won’t be writing custom generics, at least not initially, but it’s good to know what is going on.
Optional chaining
The optional chaining in TypeScript is a powerful feature that can help us with refactoring and simplifying our code. In a nutshell, it can guide our TypeScript code to ignore the execution of a statement unless a value has been provided somewhere in that statement. Let’s see optional chaining with an example:
const square = new Square();
In the preceding snippet, we create a square
object using the Square
class of the previous section. Later, we read the value of the area
method by making sure that the object has a value set before reading it:
if (square !== undefined) {
const area = square.area();
}
The previous snippet is a precautionary step in case our object has been modified in the meantime. If we do not check the object and it has become undefined
, the compiler will throw an error. However, we can use optional chaining to make the previous statement more readable:
const area = square?.area();
The character ?
after the square
object ensures that the area
method will be accessed only if the object has a value. The case where optional chaining shines is in more complicated scenarios with much more values to check, such as the following:
const width = square?.area()?.width;
In the preceding scenario, we assume that the area
property is an optional object that contains a width
property. In that case, we would need to check values for both square
and area
.
Although the optional chaining feature was added in an earlier version of TypeScript, it has become very popular in the latest versions of Angular with its support in component templates.
Nullish coalescing
The nullish coalescing feature in TypeScript looks similar to the optional chaining we learned about in the previous section. However, it is more related to providing a default value when a variable is not set. Consider the following example that assigns a value to the mySquare
variable only if the square
object exists:
const mySquare = square ? square : new Square();
The previous statement is called a ternary operator and operates like a conditional statement. If the square
object is undefined or null, the mySquare
variable will take the default value of a new square object. We can rewrite the previous expression using nullish coalescing:
const mySquare = square ?? new Square();
Although the nullish coalescing feature was added in an earlier version of TypeScript, it has become very popular in the latest versions of Angular with its support in component templates.