Skip to main content

TypeScript Objects

Introduction

Objects are fundamental building blocks in JavaScript, and TypeScript enhances them with static type checking. In this lesson, we'll explore how TypeScript handles objects, from basic syntax to advanced patterns. Understanding objects in TypeScript is crucial for creating robust, maintainable applications.

Unlike primitive types (strings, numbers, booleans), objects in TypeScript allow you to group related data and functionality together. TypeScript adds the ability to define and enforce the shape of objects, catching potential errors before runtime.

Basic Object Types

In TypeScript, you can define the shape of an object using type annotations.

Simple Object Type Annotation

typescript
// Defining an object with type annotations
let person: { name: string; age: number } = {
name: "Alice",
age: 30
};

// Accessing properties
console.log(person.name); // Output: Alice
console.log(person.age); // Output: 30

If you try to add a property that wasn't defined in the type annotation, TypeScript will raise an error:

typescript
person.job = "Developer"; // Error: Property 'job' does not exist on type '{ name: string; age: number; }'

Optional Properties

Sometimes an object might have properties that aren't always present. TypeScript lets you mark these as optional using the ? symbol:

typescript
let user: { name: string; email?: string } = {
name: "Bob"
};

// Later we can add the optional property
user.email = "[email protected]";

// This works fine because email is optional
console.log(user); // Output: { name: 'Bob', email: '[email protected]' }

Using Interfaces for Objects

While inline object types are useful, they can become unwieldy for complex objects or when the same object structure is used in multiple places. TypeScript provides interfaces as a powerful way to name and reuse object types.

Basic Interface

typescript
interface Person {
name: string;
age: number;
}

let employee: Person = {
name: "Charlie",
age: 35
};

function greet(person: Person) {
return `Hello, ${person.name}!`;
}

console.log(greet(employee)); // Output: Hello, Charlie!

Interface with Optional Properties

typescript
interface Product {
id: number;
name: string;
price: number;
description?: string;
inStock?: boolean;
}

let laptop: Product = {
id: 1,
name: "MacBook Pro",
price: 1999
};

let phone: Product = {
id: 2,
name: "iPhone",
price: 999,
description: "Latest model",
inStock: true
};

Readonly Properties

TypeScript allows you to mark properties as readonly, preventing them from being changed after initialization:

typescript
interface User {
readonly id: number;
name: string;
}

let admin: User = {
id: 1,
name: "Admin"
};

admin.name = "Super Admin"; // OK
admin.id = 2; // Error: Cannot assign to 'id' because it is a read-only property

Nested Objects

Objects can contain other objects as properties. TypeScript lets you define the structure of these nested objects:

typescript
interface Address {
street: string;
city: string;
zipCode: string;
}

interface Customer {
name: string;
address: Address;
contactNumbers: string[];
}

let customer: Customer = {
name: "Dave",
address: {
street: "123 Main St",
city: "Boston",
zipCode: "02108"
},
contactNumbers: ["555-1234", "555-5678"]
};

console.log(customer.address.city); // Output: Boston
console.log(customer.contactNumbers[0]); // Output: 555-1234

Index Signatures

Sometimes you might not know all property names ahead of time, but you do know the shape of the values. Index signatures allow you to define the types for properties that are added dynamically:

typescript
interface Dictionary {
[key: string]: string;
}

let colors: Dictionary = {};
colors.red = "#ff0000";
colors.green = "#00ff00";
colors.blue = "#0000ff";

console.log(colors.red); // Output: #ff0000

You can also mix known and index signature properties:

typescript
interface Config {
name: string;
[key: string]: string | number;
}

let appConfig: Config = {
name: "MyApp",
version: 1.0,
apiEndpoint: "https://api.example.com"
};

console.log(appConfig.name); // Output: MyApp
console.log(appConfig.version); // Output: 1

Object Methods

Objects can contain methods (functions as properties):

typescript
interface Calculator {
add(a: number, b: number): number;
subtract(a: number, b: number): number;
}

const calculator: Calculator = {
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
}
};

console.log(calculator.add(5, 3)); // Output: 8
console.log(calculator.subtract(10, 4)); // Output: 6

Intersection Types

TypeScript allows you to combine multiple object types using intersection types:

typescript
interface HasName {
name: string;
}

interface HasAge {
age: number;
}

type PersonInfo = HasName & HasAge;

let personInfo: PersonInfo = {
name: "Eve",
age: 28
};

// personInfo must have both name and age properties

Real-world Example: Product Management System

Let's build a simple product management system using TypeScript objects:

typescript
// Define the basic structure for our entities
interface Category {
id: number;
name: string;
}

interface Supplier {
id: number;
name: string;
contactPerson: string;
email: string;
phone: string;
}

interface Product {
id: number;
name: string;
price: number;
category: Category;
supplier: Supplier;
stock: number;
isAvailable: boolean;
lastUpdated: Date;
}

// Create some sample data
const electronics: Category = {
id: 1,
name: "Electronics"
};

const acmeSupplier: Supplier = {
id: 101,
name: "ACME Electronics",
contactPerson: "John Doe",
email: "[email protected]",
phone: "555-1234"
};

const laptop: Product = {
id: 1001,
name: "Premium Laptop",
price: 1299.99,
category: electronics,
supplier: acmeSupplier,
stock: 15,
isAvailable: true,
lastUpdated: new Date()
};

// Function to update stock
function updateStock(product: Product, newStock: number): Product {
return {
...product,
stock: newStock,
isAvailable: newStock > 0,
lastUpdated: new Date()
};
}

// Usage
const updatedLaptop = updateStock(laptop, 10);
console.log(`${updatedLaptop.name} - In stock: ${updatedLaptop.stock}`);
// Output: Premium Laptop - In stock: 10

This example demonstrates how to:

  1. Define complex object structures using interfaces
  2. Create relationships between objects
  3. Use immutable update patterns (returning a new object)
  4. Work with various types of properties

Advanced Pattern: Discriminated Unions

A common pattern in TypeScript is to use discriminated unions to handle objects that can be of different types:

typescript
interface Square {
kind: "square";
size: number;
}

interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}

interface Circle {
kind: "circle";
radius: number;
}

type Shape = Square | Rectangle | Circle;

function calculateArea(shape: Shape): number {
switch (shape.kind) {
case "square":
return shape.size * shape.size;
case "rectangle":
return shape.width * shape.height;
case "circle":
return Math.PI * shape.radius * shape.radius;
}
}

const mySquare: Square = { kind: "square", size: 5 };
const myRectangle: Rectangle = { kind: "rectangle", width: 4, height: 6 };
const myCircle: Circle = { kind: "circle", radius: 3 };

console.log(calculateArea(mySquare)); // Output: 25
console.log(calculateArea(myRectangle)); // Output: 24
console.log(calculateArea(myCircle)); // Output: 28.274333882308138

In this pattern, the kind property acts as a discriminant, allowing TypeScript to narrow down the type within each case of the switch statement.

Object Type vs Interface

TypeScript offers two main ways to define object types: type aliases and interfaces. Here's a comparison:

typescript
// Using an interface
interface UserInterface {
name: string;
age: number;
}

// Using a type alias
type UserType = {
name: string;
age: number;
};

The main differences are:

  • Interfaces can be extended using extends keyword
  • Interfaces can be merged if declared multiple times
  • Type aliases can create union types and other more complex types

For simple object types, either approach works well and is largely a matter of preference.

Object Destructuring with TypeScript

TypeScript fully supports JavaScript's object destructuring with added type safety:

typescript
interface Person {
name: string;
age: number;
address: {
street: string;
city: string;
};
}

function printPersonInfo(person: Person) {
// Destructuring with type safety
const { name, age, address: { city } } = person;
console.log(`${name} is ${age} years old and lives in ${city}`);
}

const person: Person = {
name: "Frank",
age: 40,
address: {
street: "456 Elm St",
city: "Chicago"
}
};

printPersonInfo(person); // Output: Frank is 40 years old and lives in Chicago

Summary

In this lesson, we've covered the essentials of working with objects in TypeScript:

  • Basic object type annotations
  • Optional and readonly properties
  • Using interfaces to define object shapes
  • Nested objects and index signatures
  • Object methods
  • Advanced patterns like intersection types and discriminated unions
  • Object destructuring with type safety

TypeScript's strong typing for objects helps catch errors early and provides great tooling support through autocompletion and documentation. By properly defining the shape of your objects, you make your code more robust and easier to maintain.

Exercises

  1. Create an interface for a Book with properties for title, author, publication year, and an optional property for genre.

  2. Define an interface for a TodoItem and a TodoList that contains an array of TodoItem objects. Implement a function that marks a todo item as complete.

  3. Create a shopping cart system with Product, CartItem, and Cart interfaces. Implement functions to add items to the cart, update quantities, and calculate the total price.

  4. Implement a discriminated union for different notification types (email, text message, push notification) with appropriate properties for each type.

Additional Resources

Understanding objects in TypeScript provides the foundation for object-oriented programming and more complex application development. Practice working with objects to become proficient in TypeScript development.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)