Type Aliases

1. Welcome and Icebreaker:

  • Welcome! If you could create an alias for any complex concept in real life to make it simpler to refer to, what would it be?

2. Outline the Session:

  • Today, we’ll learn about Type Aliases in TypeScript.
  • We’ll cover: what they are, how to define them for primitive types, union types, object types, and even generic types.
  • Learning Outcomes: You’ll be able to create custom names for types to improve code readability and reusability.
  • Let’s simplify some types!

3. Concept Explanation:

  • Type Aliases allow you to create a new name (an alias) for an existing type. They don’t create new types; they simply provide a different name to refer to a type.

  • This is particularly useful for complex types like unions, intersections, tuples, or complex object structures, making your code cleaner and easier to understand.

  • The keyword type is used to define a type alias.

  • Syntax: type AliasName = ExistingType;

  • Use Cases:

    • Simplifying Complex Types: Make long union types or object types more manageable. type StringOrNumber = string | number; type Point = { x: number; y: number };
    • Improving Readability: Give semantic meaning to a type. type UserID = string; type ProductCode = number;
    • Reusable Types: Define a complex type once and reuse it in multiple places.
    • Generic Type Aliases: Type aliases can also be generic. type Nullable<T> = T | null | undefined;
  • Type Aliases vs. Interfaces:

    • Both can be used to describe the shape of an object.
    • Key Difference: Interfaces can be implemented by classes and can be extended by other interfaces. Type aliases are more flexible and can represent unions, intersections, primitives, and more complex mapped types.
    • Declaration Merging: Interfaces support declaration merging (if you define an interface with the same name multiple times, TypeScript merges their definitions). Type aliases do not.
    • General Guideline: Use interface for defining the shape of objects or when you need features like implements or extends. Use type for unions, intersections, primitives, or more complex type manipulations.

4. Live Coding Demonstration:

console.log("--- Type Aliases for Primitives ---");
type MyString = string;
type MyNumber = number;

let myName: MyString = "Alice";
let myAge: MyNumber = 30;

console.log(`Name: ${myName}, Age: ${myAge}`);

console.log("\n--- Type Aliases for Union Types ---");
type StringOrNumber = string | number;

let identifier: StringOrNumber = "ID123";
console.log(`Identifier (string): ${identifier}`);
identifier = 456;
console.log(`Identifier (number): ${identifier}`);
// identifier = true; // Error: Type 'boolean' is not assignable to type 'StringOrNumber'.

console.log("\n--- Type Aliases for Object Types ---");
type Point = {
  x: number;
  y: number;
};

let p1: Point = { x: 10, y: 20 };
console.log(`Point p1: x=${p1.x}, y=${p1.y}`);

// Can also be used for function signatures
type MathOperation = (a: number, b: number) => number;

const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;

console.log(`Add 5 and 3: ${add(5, 3)}`);
console.log(`Subtract 5 from 3: ${subtract(3, 5)}`);

console.log("\n--- Type Aliases for Tuples ---");
type StringNumberPair = [string, number];

let userTuple: StringNumberPair = ["admin", 1];
console.log(`User Tuple: ${userTuple[0]}, ${userTuple[1]}`);

console.log("\n--- Reusable Type Aliases ---");
type User = {
  id: StringOrNumber; // Using another type alias
  name: string;
  isActive: boolean;
  location?: Point; // Optional property using another type alias
};

let user1: User = {
  id: "u101",
  name: "Bob",
  isActive: true,
  location: { x: 5, y: 15 }
};

let user2: User = {
  id: 202,
  name: "Charlie",
  isActive: false
};

function printUser(user: User): void {
  console.log(`User ID: ${user.id}, Name: ${user.name}, Active: ${user.isActive}`);
  if (user.location) {
    console.log(`  Location: x=${user.location.x}, y=${user.location.y}`);
  }
}

printUser(user1);
printUser(user2);

console.log("\n--- Generic Type Aliases ---");
type Container<T> = {
  value: T;
};

let numberContainer: Container<number> = { value: 123 };
let stringContainer: Container<string> = { value: "Hello Generics" };
let userContainer: Container<User> = { value: user1 };

console.log(`Number Container:`, numberContainer.value);
console.log(`String Container:`, stringContainer.value);
console.log(`User Container Name:`, userContainer.value.name);

type Nullable<T> = T | null;
let nullableString: Nullable<string> = "I am a string";
console.log(`Nullable String: ${nullableString}`);
nullableString = null;
console.log(`Nullable String (now null): ${nullableString}`);
// nullableString = undefined; // Error if strictNullChecks is on and undefined is not part of Nullable

// Example: Type alias for a function that takes a generic container
type ProcessContainer<T> = (container: Container<T>) => void;

const processNumberContainer: ProcessContainer<number> = (container) => {
  console.log(`Processing number: ${container.value * 2}`);
};

processNumberContainer(numberContainer);

5. Tasks:

  • Short Exercise:
    1. Primitive Alias: Create a type alias Age for the number type. Declare a variable of type Age.
    2. Union Alias: Create a type alias StatusCode that can represent HTTP status codes like 200, 404, or 500 (use a union of number literals).
    3. Object Alias: Create a type alias Product for an object with properties: id (string or number), name (string), price (number), and an optional description (string).
    4. Function Signature Alias: Create a type alias Logger for a function that takes a string message and returns void.
    5. Generic Alias: Create a generic type alias Maybe<T> that represents a value of type T or null or undefined.
    6. Using Aliases: Declare variables for each of the aliases you created and initialize them with appropriate values. For the Product and Logger, also try to use them (e.g., create a product object, create a logger function).