Because TypeScript supports generics, it can actually leverage them for its own libraries.
If you look at the lib.es5.d.ts (https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es5.d.ts) file (which is part of TypeScript) and search for Array<T>, you'll stumble upon this definition:
interface Array<T> { ... push(...items: T[]): number; pop(): T; ... }
As you can see, the Array interface accepts a generic parameter (inside the angle brackets: <>) named T. This T is actually just a label used to refer to a type that will be defined/given at runtime. That same label is then used in function definitions.
If we observe the push method, we can see that it accepts an array of T elements. The pop method returns T.
The following is how you can use this:
let persons: Array<Person> = [];
This syntax is the clearest because you are using angle brackets, which clearly indicate that you're passing a type parameter when creating the Array instance. However, note that the equivalent shorthand notation is generally recommended, as we discussed earlier in Chapter 2, Building TodoIt - Your Own Web Application with TypeScript:
let persons: Person[] = [];
By passing in our Person type, we are allowing the Array function to accept and return our generic type:
let persons: Array<Person> = []; // <1> add a single person persons.push(new Person("John", "Doe")); // <2> add multiple persons persons.push( new Person("John", "McClane"), new Person("John", "Smith"), new Person("John", "Dunbar") ); // <3> retrieve a single person const person:Person = persons.pop() as NonNullable<Person>; // <4> loop over all entries persons.forEach(person => { console.log(`Hello ${person.firstName} ${person.lastName}`); }); // <5> classic for loop over all entries for(let person: Person, i:number=0; i < persons.length; i++) { person = persons[i]; console.log(`Hello ${person.firstName} ${person.lastName}`); }
// <6> for..of loop to also loop over all the entries
for(const person of persons) {
console.log(`Hello ${person.firstName`);
}
In the preceding example, there are multiple things to point out:
In <1>, you can see that we can pass a Person object. Actually, here, we can only pass Person instances:
In <2>, we can pass multiple items if we wish to.
In <3>, note that we need to do a type cast even though we have passed a generic type in. The reason for this is that the pop() method returns Person | Undefined simply because the array might be empty when we call pop, and therefore could return undefined. The NonNullable type that we use here is part of the standard TypeScript library and is defined as type NonNullable<T> = T extends null | undefined ? never : T;. It simply removes null and undefined from the given type; so, in our case, it tells TypeScript don't worry, it won't be null or undefined here. Of course, you should still use this carefully! The | and ? characters in the type definition of NonNullable are two advanced concepts of the type system that we haven't discussed yet, that is, union types and conditional types, respectively. We will review these in more detail shortly.
In <4>, we simply loop over all the entries in the array. We don't need to use type casts since the forEach loop gives us Person objects.
In <5>, we do the same using a classic for loop, which you won't see many occurrences of in this book.
Finally, in <6>, we do the same using a for..of loop, which we don't use much in this book either.
As you can see, thanks to the fact that the Array type is generic (that is, it accepts type parameters to be passed in), we can avoid type casts in most cases and we can also benefit from an increase in type safety.