š„Advanced Typescript
Learn how to use important advanced concepts in typescript with examples

Typescript is a language built on top of Javascript that was developed to allow developers to work with types in Javascript, which originally was developed as a loosely language - without any types.
Today we will go over the presentation given by Bruno Francisco in a meetup organized in codotto.com.
What is typescript
Typescript is a programming language that is a superset of Javascript. It was developed and maintained by Microsoft. Typescript adds optional static typing and class-based object-oriented programming to Javascript. This allows for improved code readability and reliability. Because Typescript is a superset of Javascript, any existing Javascript code can be easily incorporated into a Typescript project. In addition, Typescript code can be compiled into plain Javascript that can be run on any platform that supports Javascript. Overall, Typescript provides a way to write scalable and maintainable Javascript code.
Typescript vs Javascript
Typescript and Javascript are both programming languages that are used for web development. Javascript is the most popular programming language for web development, and it is supported by all modern web browsers. Typescript is a superset of Javascript, which means that any valid JavaScript code is also valid Typescript code. However, Typescript adds additional features to JavaScript that make it easier to write large and complex programs.
One of the key differences between Typescript and Javascript is that Typescript is a typed language, while Javascript is a dynamically typed language. This means that in Typescript, variables and functions must be declared with a specific type, such as number or string. In contrast, in Javascript, the type of a variable or function can change at runtime. This makes Typescript a more reliable and predictable language than Javascript.
Another major difference between Typescript and Javascript is that Typescript supports object-oriented programming concepts, such as classes and interfaces, while JavaScript does not - in the ES6 spec, it is possible to use class natively without the need of Typescript. This means that in Typescript, it is possible to define classes and create objects from those classes, whereas in Javascript, objects are defined and created using a different syntax. This makes Typescript a better choice for large and complex applications that require object-oriented programming.
Overall, Typescript and Javascript are both powerful languages for web development. However, Typescript offers additional features that make it better suited for large and complex projects. It is also easier to maintain and scale Typescript code, thanks to its static typing and object-oriented programming features.
// Pure Javascript
function getFullName(user) {
return `${user.firstName} ${user.lastName}`;
}
// Typescript
interface IUser {
firstName: string;
lastName: string;
}
function getFullName(user: IUser) {
return `${user.firstName} ${user.lastName}`;
}
The important concepts we will be looking at
There are multiple advanced concepts that are used in Typescript. Unfortunately, we won't be looking at all of them today - maybe in a different talk in the subject - but the ones that we will be looking at are the ones that will most likely help you on your day-to-day life as a developer.
These are the concepts we will be looking at:
- Utility types
- Generic types
- "in operator"
- Assert functions
- Conditional types
- Guard operators
extends
keyword
1. Utility types
In TypeScript, "utility types" are types that are defined in the standard Typescript library and can be used to perform common operations on other types. These types are not intended to be used directly, but rather to be used as a building block to create more complex types.
Utility types are often used to create new types by combining or modifying existing types. For example, the Pick
utility type allows you to create a new type by selecting a specific set of properties from an existing type. The Exclude
utility type allows you to create a new type by excluding specific properties from an existing type.
Here is an example of using the Pick
utility type to create a new type:
type Person = {
name: string;
age: number;
city: string;
};
// PersonInfo is a new type with only the 'name' and 'city' properties from the Person type
type PersonInfo = Pick<Person, 'name' | 'city'>;
In this example, the Person type is defined with three properties: name
, age
, and city
. Then, the Pick
utility type is used to create a new type called PersonInfo
, which includes only the name
and city
properties from the Person
type.
Utility types can be a valuable tool in Typescript, as they can help you create new and more specific types by combining or modifying existing types. This can make your code more readable and maintainable, as well as more flexible and reusable.
Where we use utility types in Codotto
In codotto we define our types that come from the backend in interfaces. Here is an example of how we define an interface for UserTalk
import type { CreatedAt, RecordID } from '@/services/api';
import type { UserCodec } from '@/services/api/resources/users/codec';
export interface UserTalkCodec {
id: RecordID;
title: string;
description: string;
duration: number;
presentationUrl: string | null;
createdAt: CreatedAt;
user: UserCodec;
feedbackInfo?: {
count: number;
average: number;
};
}
We also define what we should send to the backend whenever we would like to create a resource - in this example, whenever we want to create a UserTalk
export type UserTalkCreatable = Pick<
UserTalkCodec,
'title' | 'description' | 'presentationUrl'
>;
Notice how we make it clear that whenever we want to create a UserTalk
, we need to send fields that are dependent on UserTalk
interface. Imagine that we would add a new field called intendedAudience
to our UserTalk
. As soon as we add it to the UserTalk
interface we would need to update our code base to send this new field intendedAudience
to our backend as well
export interface UserTalkCodec {
// ...
intendedAudience: 'junior' | 'middle' | 'senior' | 'manager';
}
2. Generic types
In TypeScript, generic types are types that are not specific to any one particular data type. Instead, they can be used with a variety of data types. This allows for greater flexibility and reusability in your code.
For example, consider the following function that takes an array of elements and returns the first element in the array:
function firstElement<T>(elements: T[]): T {
return elements[0];
}
In this function, the generic type T
is used for the elements in the array, as well as the return type of the function. This means that T
can be any data type, such as a number
, string
, or object
. To use this function with a specific data type, you can specify the type when calling the function, like this:
const numbers = [1, 2, 3];
const firstNumber = firstElement<number>(numbers);
const strings = ['hello', 'world'];
const firstString = firstElement<string>(strings);
In this example, the firstElement
function is called twice, once with the numbers array and once with the strings array. In each case, the generic type T
is replaced with the specific type that is being used (number
and string
, respectively). This allows the function to operate on the array elements of the specified type and return a value of the same type.
Overall, generic types in TypeScript provide a way to write flexible and reusable code that can be used with multiple data types. This can help you avoid repeating the same code for different data types and make your code easier to maintain.
Where we use generics in Codotto
Our backend in codotto is using Laravel and Laravel has a pretty standard way of returning paginated results. Whenever we fetch the list of groups we get a response that looks like this
{
"data": [
{
"id": "c562a066-bde6-4d09-ae65-cabbd64c15ee",
"title": "test title",
"description": "I want to see the meetup room !<br>",
"slug": "test-title",
"image": "https://api-devotto-prod-files.s3.eu-central-1.amazonaws.com/groups/covers/4d46cd6f-756c-46e9-87c2-39a4b3b03c54",
// ...
}
],
"links": {
"first": "https://api.codotto.com/groups?page=1",
"last": "https://api.codotto.com/groups?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
// ...
}
}
The links
and meta
will always look the same - regarding to types. The links.first
, links.last
will always be a string
while links.prev
and links.next
can either be a string
or nullable
. The only part that will be different is the data
key. This data
key can contain multiple types data inside of it at a time. Sometimes we can have meetups, sometimes we can have groups or users. This is a perfect example to define a generic. We can create a PaginatableRecord
interface that receives a generic whenever we want to use it
export interface PaginatableRecord<T> {
data: T[];
links: Links;
meta: Meta;
}
Then we can use this PaginatableRecord
as such:
const listCities: PaginatableRecord<CityCodec> = () => { ... };
const listGroups: PaginatableRecord<GroupCodec> = () => { ... };
const listMeetups: PaginatableRecord<MeetupCodec> = () => { ... };
3. "in" operator
In TypeScript, the "in operator" is a binary operator that is used to determine whether a property with a given name exists in an object. This operator returns true
if the property exists, and false
if it does not.
The "in operator" is typically used in a conditional statement, such as an if
statement, to check whether a property exists in an object before accessing or modifying its value. Here is an example of using the "in operator" in a conditional statement:
const person = {
name: 'John Doe',
age: 35,
eyeColor: 'blue',
};
if ('name' in person) {
console.log(`The person's name is ${person.name}.`);
}
In this example, the "in operator" is used to check whether the name
property exists in the person
object. If it does, the code inside the if
statement is executed, and the person's name is logged to the console. If the name
property does not exist, the code inside the if
statement is skipped.
The "in operator" can also be used with arrays to check whether an index exists in the array. For example, the following code uses the "in operator" to check whether the index 2
exists in the numbers
array:
const numbers = [1, 2, 3, 4, 5];
if (2 in numbers) {
console.log(`The number at index 2 is ${numbers[1]}.`);
}
Overall, the "in operator" in TypeScript is a useful tool for checking whether a property or index exists in an object or array before accessing or modifying its value. This can help you avoid runtime errors and make your code more reliable and predictable.
Where do we use "in operator" in Codotto
Currently we do not have any real usage of the "in operator" in our codebase but a good example would be the case where we have a codec for Meetup
and another for MeetupOwner
, where one brings more information about the meetup if the logged user is the owner
of the meetup
interface MeetupCodec {
id: ResourceID;
title: string;
}
interface MeetupOwner extends BaseMeetup {
numberConfirmedAttendees: number;
checkInConfirmationCode: string;
}
Then, we can use the "in operator" to check if the Meetup
being returned from the backend is a MeetupOwner
type:
export function isMeetupOwner(meetup: Meetup | MeetupOwner): Meetup | MeetupOwner {
return 'numberConfirmedAttendees' in meetup;
}
4. Assert functions
In TypeScript, "assert functions" are functions that are used to test the type of a value at runtime. These functions are typically used in unit tests to ensure that a value has the expected type.
An assertion function specifies, in its signature, the type predicate to evaluate. For instance, the following function ensures a given value be a string
:
function isString(data: unknown): asserts data is string {
if (typeof data !== "string") {
throw new Error("Value should be a string");
}
}
Where do we use "assert functions" in Codotto
In our Q&A
sections, you can react to questions made to a speaker in a meetup or to the answers. The main difference is we removed the "thumbs down" emoji from the reactions. We have the following enumeration to represent the "reactions" in our application
export enum ReactionType {
THUMBS_UP = 'thumbs_up',
THUMBS_DOWN = 'thumbs_down',
SMILE = 'smile',
TADA = 'tada',
THINKING_FACE = 'thinking_face',
HEART = 'heart',
ROCKET = 'rocket',
EYES = 'eyes',
}
Whenever we update a reaction - create or delete - for Q&A
section we want to make sure that we are only sending all reactions that are not thumbs_down
. For that we have an assert function
function assertsIsPositiveReaction(
reaction: ReactionType
): asserts reaction is ReactionType.THUMBS_UP | ReactionType.SMILE | ReactionType.TADA | ReactionType.THINKING_FACE | ReactionType.HEART | ReactionType.ROCKET | ReactionType.EYES {
if (reaction === ReactionType.THUMBS_DOWN) {
throw new Error('Reaction should not be thumbs_down');
}
}
5. Conditional types
In TypeScript, conditional types are a feature that allows you to create new types based on the evaluation of a condition at compile time. This can be useful for defining more flexible and reusable types that can adapt to different conditions.
Conditional types are defined using the T extends U ? X : Y
syntax, where T
is a type parameter, U
is a type constraint, X
is the type that is chosen if the condition is true, and Y
is the type that is chosen if the condition is false. Here is an example of using a conditional type
type IsString<T> = T extends string ? T : never;
In this example, the IsString
type is defined using a conditional type. It takes a type parameter T
and defines a type constraint that requires T
to be a string
type. If the T
type satisfies this constraint (i.e., if it is a string
), the IsString
type is the same as the T
type. Otherwise, the IsString
type is the never
type.
To use the IsString
type, you can specify a type argument when calling the type. For example, the following code uses the IsString
type to define the name
variable
const name: IsString<string> = 'John Doe';
In this example, the IsString
type is called with the string
type as the type argument. Since the string
type satisfies the type constraint of the IsString
type, the name
variable is typed as the string
type.
Conditional types can be a powerful tool in TypeScript, as they allow you to create types that can adapt to different conditions. This can make your code more flexible and reusable, and can help you avoid repeating the same type definitions for different scenarios.
Where do we use conditional types in Codotto
Currently we do not have any implementations of conditional types in codotto.
6. Guard operators
In TypeScript, "guard operators" are a set of operators that are used to narrow the type of a value based on certain conditions. These operators allow you to ensure that a value has the expected type, and to handle the value differently depending on its type.
There are several guard operators in Typescript, including the typeof
operator, the instanceof
operator, and the in
operator. These operators can be used in combination with type guards, which are expressions that evaluate to a boolean value and narrow the type of a value based on the result of the evaluation.
Here is an example of using the typeof
operator and a type guard to narrow the type of a value
function getLength(value: unknown): number {
if (typeof value === 'string') {
return value.length;
}
return 0;
}
In this example, the getLength()
function takes a value of type unknown
as an argument and returns its length if the value is a string
, or 0
if it is not. The typeof
operator is used in the type guard to check whether the value
is a string
, and if it is, the value.length
property is accessed. Since the type guard narrows the type of the value
to string
.
Where do we use guards operators in codotto
In codotto we have a component for choosing the image that you would like to crop. This component is used in updating your profile picture, updating a cover image to your meetup and many other places.
This component has a prop image
that can either be Blob
or string
- it will be a Blob
if we are using the component to upload an image to a server or string
if we are receiving the image as a link from the backend.
The underlying plugin to actually crop the image only accepts URLs and not Blob
. So we can use a guard operator to help us transform a Blob
into a base64
representation or just return the URL in case it is a string
const imageUrl = computed(() => {
if (!props.image) return '';
if (process.env.CLIENT) {
if (props.image instanceof Blob) {
return URL.createObjectURL(props.image);
}
}
return props.image;
});
7. extends keyword
In TypeScript, the extends
keyword is used to create a derived class from an existing class. This allows you to create a new class that inherits the properties and methods of the existing class, and to add or override members to create a custom implementation.
Here is an example of using the extends
keyword to create a derived class
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
move(distance: number) {
console.log(`${this.name} moved ${distance} meters.`);
}
}
class Dog extends Animal {
bark() {
console.log(`${this.name} barked.`);
}
}
In this example, the Animal
class is defined with a name
property and a move()
method. The Dog
class is then defined as a derived class of Animal
using the extends
keyword. The Dog
class inherits the name
property and the move()
method from the Animal
class, and it adds a new bark()
method.
To use the Dog
class, you can create an instance of the class and call its methods, like this
const dog = new Dog('Fido');
dog.move(10); // logs "Fido moved 10 meters."
dog.bark(); // logs "Fido barked."
In this example, an instance of the Dog
class is created and assigned to the dog
variable. The move()
and bark()
methods are then called on the dog
instance. Since the Dog
class extends the Animal
.
The extends
keyword is usually used in classes but it can also be extended to objects. We can safeguard that a parameter being passed to a function extends
from a subset of type
interface Group {
title: string;
}
interface User {
firstName: string;
lastName: string;
}
function getTitle<T extends {title: string}>(value: T): string {
return value.title;
}
const group: Group = { title: 'Javascript Miami' };
const user: User = { firstName: 'John', lastName: 'Doe' };
const groupTitle = getTitle(group);
// This will give an error since `user` doesn't have property `title`
const user = getTitle(user);
Where do we use extends keyword in Codotto
In codotto you can react to many resources, such as meetup messages and questions made to speakers during the meetup. These resources that can be "reactable" are received from the backend with two properties that are well known in the frontend: reactionsGroupedBy
and loggedUserReactions
.
reactionsGroupedBy
tells us which reactions this resource has and how many reactions. The loggedUserReactions
tells us which reactions the current logged user have reacted to this resource. A concrete of example can be
{
title: "Javascript Miami",
reactionsGroupedBy: [
{ reactionType: 'heart', totalReactions: 30 },
{ reactionType: 'rocket', totalReactions: 1 },
],
loggedUserReactions: ['heart', 'rocket']
}
We have a service that will update this object reactionsGroupedBy
and loggedUserReactions
properties. This service should receive an object that contains these two properties - it doesn't matter to the service if it is a GroupCodec
or MeetupMessageCodec
. For this we can define a generic that should extend an object that has these two properties - reactionsGroupedBy
and loggedUserReactions
export interface ReactionsGroupedBy {
totalReactions: number;
reactionType: ReactionType;
}
export const updateReactions = <T extends { reactionsGroupedBy: ReactionsGroupedBy[]; loggedUserReactions?: ReactionType[] }>(
reactable: T,
reaction: ReactionType
): T => {
// Logic to update the reactions of the object
}
Conclusion
In conclusion, Typescript is a statically-typed programming language that is built on top of Javascript. It adds support for advanced features such as interfaces, classes, and modules, as well as strong typing and type checking. These features can help developers write more reliable and maintainable code, and can improve the overall quality of their projects. Additionally, Typescript code can be easily transpiled to Javascript, allowing it to run on any platform that supports Javascript. Overall, Typescript is a powerful and versatile language that is well-suited for building complex and scalable applications.
Typescript is widely used in industry, and it is supported by many popular frameworks and libraries, such as Angular, React, and Node.js. It has a growing community of users and contributors, and it is regularly updated with new features and improvements.
The extends
keyword, utility types, generic types, "in operator", assert functions, conditional types, and guard operators are all important features of Typescript. These features provide a powerful and flexible type system that can help you write high-quality and maintainable code.
The extends
keyword is used to create a derived class from an existing class, allowing you to inherit its properties and methods and to add or override members to create a custom implementation. Utility types are types that are defined in the standard Typescript library and can be used to perform common operations on other types. Generic types are types that are not specific to any one particular data type, allowing for greater flexibility and reusability in your code.
The "in operator" is a binary operator that is used to determine whether a property with a given name exists in an object. Assert functions are functions that are used to test the type of a value at runtime, and can be used in unit tests to ensure that a value has the expected type. Conditional types are a feature that allows you to create new types based on the evaluation of a condition at compile time, allowing for more flexible and reusable types. Guard operators are a set of operators that are used to narrow the type of a value based on certain conditions, allowing you to ensure that a value has the expected type and to handle it differently depending on its type.
Overall, these features of Typescript provide a rich and powerful type system that can help you write high-quality and maintainable code. They can be a valuable tool for any developer who wants to take advantage of the benefits of static type checking in their Javascript projects.