Lesson 2
Building a Type-Safe Address Book in TypeScript
Introduction

Welcome! Today, we will explore creating a simple address book application using TypeScript's Map. This task will help you understand how to manipulate Map in TypeScript, focusing on adding, retrieving, and deleting entries. By the end of this lesson, you'll have a solid grasp of these fundamental operations while ensuring data integrity through the type annotations that TypeScript provides.

Introducing Methods to Implement

In this task, we will implement three methods to manage our address book, using type annotations to ensure code reliability:

  • addContact(name: string, phoneNumber: string): boolean: Adds a new contact. Returns false if the contact already exists; otherwise, it adds the contact and returns true. In this task, let's assume phone numbers do not change, so it's not allowed to overwrite the existing contact's number.
  • getContact(name: string): string | undefined: Retrieves the phone number for a given name. Returns undefined if the contact does not exist.
  • deleteContact(name: string): boolean: Deletes a contact with the given name. Returns true if the contact exists and is deleted, false otherwise.

Let's break down each method in detail in the next sections.

Step 1: Initial Setup

Before we begin implementing the methods in our address book, we need to set up our TypeScript class that will hold the contacts using a Map.

Here's how to set up the initial structure:

TypeScript
1class AddressBook { 2 contacts: Map<string, string>; 3 4 constructor() { 5 this.contacts = new Map<string, string>(); 6 } 7} 8

In this initial setup:

  • We define the AddressBook class with a contacts property, which is a Map that holds the name of the contact as the key and the phone number as the value.
  • We initialize the contacts property in the constructor, ensuring each instance of AddressBook starts with an empty map.
Step 2: Implementing 'addContact'

This method adds a new contact to the address book with the given name and phoneNumber. If the contact already exists, it returns false. Otherwise, it adds the contact and returns true.

Question: Why do you think we need to check if the contact already exists?

Answer: To avoid duplicating existing entries. Also, if a contact with the same name already exists, we shouldn't allow overwriting its phone number in this method, as it's only for creation.

Here is the method implementation:

TypeScript
1addContact(name: string, phoneNumber: string): boolean { 2 if (this.contacts.has(name)) { 3 return false; 4 } 5 this.contacts.set(name, phoneNumber); 6 return true; 7}

In this method:

  • We use type annotations such as name: string and phoneNumber: string to ensure that inputs adhere to specified types.
  • We verify if the contact already exists using if (this.contacts.has(name)).
  • If it exists, we return false.
  • If it doesn't exist, we add it to our Map and return true.
Step 3: Implementing 'getContact'

This method retrieves the phone number associated with a given name. If the contact does not exist, it returns undefined.

Question: What do we gain by returning undefined when a contact doesn't exist?

Answer: It provides a clear indicator that the contact is not in the address book, allowing us to handle such cases gracefully.

Here is the method implementation:

TypeScript
1getContact(name: string): string | undefined { 2 return this.contacts.get(name); 3}

In this method:

  • Type annotations name: string for the parameter and string | undefined for the return type ensure that we expect a string or an undefined value returned.
Step 4: Implementing 'deleteContact'

This method deletes a contact with the given name. If the contact exists and is deleted, it returns true. If the contact does not exist, it returns false.

Here is the method implementation:

TypeScript
1deleteContact(name: string): boolean { 2 return this.contacts.delete(name); 3}

The deleteContact method attempts to remove a contact with the specified name by utilizing the delete method of the Map, which inherently returns a boolean. This boolean indicates success (true) if the contact was found and removed or failure (false) if no such contact existed.

Step 5: Final Code and Usage

Let's consolidate everything we've learned into the final implementation of our AddressBook class, and provide a simple example of how you might use it.

Here is the complete implementation of the AddressBook class with all three methods:

TypeScript
1class AddressBook { 2 contacts: Map<string, string>; 3 4 constructor() { 5 this.contacts = new Map<string, string>(); 6 } 7 8 addContact(name: string, phoneNumber: string): boolean { 9 if (this.contacts.has(name)) { 10 return false; 11 } 12 this.contacts.set(name, phoneNumber); 13 return true; 14 } 15 16 getContact(name: string): string | undefined { 17 return this.contacts.get(name); 18 } 19 20 deleteContact(name: string): boolean { 21 return this.contacts.delete(name); 22}

Here's an example demonstrating how to use the AddressBook class:

TypeScript
1const myAddressBook = new AddressBook(); 2 3// Adding contacts 4console.log(myAddressBook.addContact('Alice', '123-456-7890')); // true 5console.log(myAddressBook.addContact('Bob', '987-654-3210')); // true 6console.log(myAddressBook.addContact('Alice', '111-111-1111')); // false, already exists 7 8// Retrieving contacts 9console.log(myAddressBook.getContact('Alice')); // '123-456-7890' 10console.log(myAddressBook.getContact('Charlie')); // undefined 11 12// Deleting contacts 13console.log(myAddressBook.deleteContact('Bob')); // true 14console.log(myAddressBook.deleteContact('Charlie')); // false

This example demonstrates how to add, retrieve, and delete contacts using the implemented methods in the AddressBook class.

Why Maps are Efficient for These Tasks

Maps are particularly efficient for managing an address book due to several reasons, enhanced by TypeScript's strong typing:

  • Efficient Lookups and Insertions: Maps provide average O(1) time complexity for lookups, insertions, and deletions. This means managing contacts is very fast, even with a large number of them.
  • Preservation of Insertion Order: Maps maintain the order of entries according to their insertion sequence, which might be useful for displaying contacts in the order they were added.
  • Type Safety with Keys and Values: TypeScript enforces type safety, ensuring all keys and values in the Map strictly follow the defined types, reducing bugs caused by unintended type mismatches.
Summary

In this lesson, we implemented a type-safe address book using TypeScript's Map, covering three key methods: addContact, getContact, and deleteContact. The use of Map provided efficient O(1) average time complexity for lookups, insertions, and deletions, as well as maintenance of insertion order, enhancing the usability and performance of our address book application. Through these methods, we've demonstrated how TypeScript can enforce type safety while interacting with Map, providing reliable and efficient data management.

Now that you're familiar with these fundamental operations, let's dive into the practice exercises to reinforce your understanding and skills!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.