Phase 1 — Prerequisites & TypeScript Deep Dive to Learn Angular

Let's be very clear about one thing first.

Most people jump straight into Angular and then struggle. They don't know why things work the way they do. They copy-paste code without understanding it. Then they get stuck on something simple and have no idea how to fix it.

We are NOT doing that.

We are going to build a rock-solid foundation first. By the time you write your first Angular component, you will understand exactly what is happening and why.


Why TypeScript First?

Angular is written 100% in TypeScript. Every file you create will be a .ts file. If you don't understand TypeScript, you will be confused by Angular syntax from line one.

But more importantly — TypeScript teaches you to think like a structured programmer. It forces you to think about what kind of data you're working with before you work with it. This habit alone will make you a significantly better developer.


Setting Up Your Environment

Before writing a single line of code, let's set up your workspace properly.

Step 1 — Install Node.js

Go to nodejs.org and download the LTS version. LTS means Long Term Support — it's the stable version. Don't download the latest/current version, it may have bugs.

After installing, open your terminal and verify:

node --version
npm --version

You should see version numbers. If you do, Node is installed correctly.

Node.js itself is a JavaScript runtime — it lets you run JavaScript outside the browser. But more importantly for us right now, it comes with npm (Node Package Manager), which is how we install Angular and TypeScript.

Step 2 — Install TypeScript

npm install -g typescript

The -g flag means global — install it on your entire computer, not just one project.

Verify:

tsc --version

tsc stands for TypeScript Compiler. It converts your TypeScript code into regular JavaScript that browsers understand.

Step 3 — Install a Code Editor

Use Visual Studio Code. It's free, it's built by Microsoft (the same people who made TypeScript), and it has the best TypeScript support of any editor.

Download from code.visualstudio.com

Essential VS Code Extensions to install:

  • Angular Language Service — gives you intellisense inside Angular HTML templates
  • Prettier — Code Formatter — auto-formats your code
  • ESLint — catches code quality issues
  • Auto Rename Tag — renames closing tags automatically

Step 4 — Create your TypeScript practice folder

mkdir typescript-practice
cd typescript-practice

Create a file called index.ts inside it. This is where we'll practice everything.

To run any TypeScript file:

tsc index.ts        ← this compiles index.ts into index.js
node index.js       ← this runs the compiled file

Or install ts-node to run TypeScript directly without manually compiling:

npm install -g ts-node
ts-node index.ts    ← compile + run in one command

We'll use ts-node throughout this guide because it's faster.

TypeScript — Complete Deep Dive

Chapter 1 — The Type System — Why It Exists and How It Thinks

Before we write any code, let's understand the problem TypeScript is solving.

In JavaScript, you can do this:


    let userId = 5;
    userId = "hello";        // No error. JavaScript is fine with this.
    userId = true;           // Still no error.
    userId = { name: "x" }; // JavaScript doesn't care.

This seems flexible but it's actually dangerous. Imagine you have a function that calculates a discount based on userId. If userId is accidentally a string, your calculation breaks silently. No error. Just wrong results. And you won't know why until a user reports a bug.

TypeScript fixes this by adding a type system. You declare what kind of value a variable holds, and TypeScript makes sure you never accidentally put the wrong kind of value in it.


    let userId: number = 5;
    userId = "hello";   // ERROR: Type 'string' is not assignable to type 'number'

Now the error shows up immediately in your editor — before you even run the code.

This is called static type checking. Static means it happens at compile time (when you write code), not at runtime (when the code runs). Catching bugs at compile time is infinitely better than catching them in production.

Chapter 2 — Primitive Types — The Building Blocks

TypeScript has 7 primitive types. Let's cover all of them deeply.

2.1 — string

Represents any text value.


    let firstName: string = "Rahul";
    let lastName: string = 'Sharma';           // single quotes also work
    let greeting: string = `Hello, ${firstName} ${lastName}!`; // template literals work too

    console.log(greeting); // Hello, Rahul Sharma!

What you CANNOT do:


    let firstName: string = "Rahul";
    firstName = 42;         // ERROR: Type 'number' is not assignable to type 'string'
    firstName = true;       // ERROR: Type 'boolean' is not assignable to type 'string'


2.2 — number

Represents any numeric value — integers, decimals, negative numbers. Unlike some languages (Java, C++), TypeScript doesn't have separate int and float types. Everything is just number.


    let age: number = 25;
    let price: number = 999.99;
    let temperature: number = -5;
    let bigNumber: number = 1_000_000;    // underscores for readability — still valid
    let hex: number = 0xff;               // hexadecimal
    let binary: number = 0b1010;          // binary

    console.log(age, price, temperature);  // 25  999.99  -5


2.3 — boolean

Represents true or false. That's it. Nothing else.


    let isLoggedIn: boolean = true;
    let hasSubscription: boolean = false;
    let isAdmin: boolean = false;

    if (isLoggedIn && hasSubscription) {
        console.log("Show premium content");
    } else if (isLoggedIn && !hasSubscription) {
        console.log("Show free content");
    } else {
        console.log("Please login");
    }


Output:

Show free content

2.4 — null and undefined

These two are different and understanding the difference matters.

undefined = a variable has been declared but not assigned a value yet. null = a variable has been intentionally set to "no value".


    let userAddress: string | null = null;
    // The user exists but hasn't provided their address yet.
    // We're explicitly saying: this field exists but is empty.

    let draftTitle: string | undefined = undefined;
    // This hasn't been set yet at all.

The string | null syntax is called a Union Type. We'll cover this properly in Chapter 6. For now just read it as "this can be a string OR null".


2.5 — any

any is TypeScript's escape hatch. A variable of type any can hold any value, just like regular JavaScript.


    let data: any = 5;
    data = "hello";     // no error
    data = true;        // no error
    data = { x: 1 };   // no error

Use any sparingly. When you use any, you're essentially turning off TypeScript's protection for that variable. It's sometimes necessary (like when working with third-party libraries that don't have TypeScript support), but don't make a habit of it.

If you find yourself writing any everywhere, you're not actually writing TypeScript — you're writing JavaScript with extra steps.


2.6 — unknown

unknown is the safer alternative to any. A variable of type unknown can hold any value, but before you can use it, you have to check what type it actually is.


    let input: unknown = "Hello World";

    // You CANNOT do this:
    // console.log(input.toUpperCase()); // ERROR: Object is of type 'unknown'

    // You MUST check the type first:
    if (typeof input === "string") {
        console.log(input.toUpperCase()); // Now this works: "HELLO WORLD"
    }

Use unknown instead of any when you genuinely don't know the type ahead of time. It's safer because TypeScript forces you to verify the type before using it.


2.7 — never

never represents a value that never occurs. This is used for functions that never return (they always throw an error or run forever).


    function throwError(message: string): never {
        throw new Error(message);
        // This function never returns normally, so return type is 'never'
    }

    function infiniteLoop(): never {
        while (true) {
            // runs forever
        }
    }

You won't use never much as a beginner, but you'll encounter it in error messages sometimes.


Chapter 3 — Type Inference — TypeScript is Smart

Here's something important: you don't always need to write the type explicitly. TypeScript can figure it out on its own.


    let name = "Rahul";         // TypeScript infers: name is string
    let age = 25;               // TypeScript infers: age is number
    let isActive = true;        // TypeScript infers: isActive is boolean

If you hover over these variables in VS Code, it will show you the inferred type.

This means you don't always need to write:


    let name: string = "Rahul";

You can just write:


    let name = "Rahul";

And TypeScript will still give you an error if you try to assign a number to it later.

When should you write the type explicitly?

  • When declaring a variable without assigning a value yet: let result: string;
  • When the inferred type isn't specific enough
  • In function parameters and return types — always be explicit here for clarity

Chapter 4 — Arrays and Tuples

4.1 — Arrays

There are two ways to type an array:


    // Method 1 — using brackets notation (more common)
    let fruits: string[] = ["apple", "mango", "banana"];
    let scores: number[] = [95, 87, 72, 100];
    let flags: boolean[] = [true, false, true];

    // Method 2 — using generic notation
    let fruits2: Array<string> = ["apple", "mango"];
    let scores2: Array<number> = [95, 87];

Both are valid. Use whichever feels more readable to you. Most Angular developers use Method 1.

Working with arrays:


    let products: string[] = ["Laptop", "Phone", "Tablet"];

    // Add to end
    products.push("Keyboard");
    console.log(products); // ["Laptop", "Phone", "Tablet", "Keyboard"]

    // Remove from end
    products.pop();
    console.log(products); // ["Laptop", "Phone", "Tablet"]

    // Find an item
    let index = products.indexOf("Phone");
    console.log(index); // 1

    // Filter items
    let longNames = products.filter(p => p.length > 5);
    console.log(longNames); // ["Laptop", "Tablet"]

    // Map (transform) items
    let upperProducts = products.map(p => p.toUpperCase());
    console.log(upperProducts); // ["LAPTOP", "PHONE", "TABLET"]

    // Find one item
    let found = products.find(p => p === "Phone");
    console.log(found); // "Phone"

    // Check if item exists
    let exists = products.includes("Laptop");
    console.log(exists); // true

These array methods are used CONSTANTLY in Angular. Especially filter, map, and find. Know them deeply.


4.2 — Tuples

Tuples in TypeScript are fixed-length arrays where each position has a specific, known type.

Basic Syntax


    let point: [number, number] = [10, 20];
    let entry: [string, number] = ["Alice", 30];

The order and types are enforced — swapping them causes a compile error.

Why Use Tuples?

Regular arrays are homogeneous (number[], string[]). Tuples let you group different types together with positional meaning, without needing a full object.

Common Use Cases

1. Returning multiple values from a function


    function getMinMax(nums: number[]): [number, number] {
        return [Math.min(...nums), Math.max(...nums)];
    }

    const [min, max] = getMinMax([3, 1, 4, 1, 5]);

2. Key-value pairs


    const pair: [string, number] = ["age", 25];

3. React-style hooks (this is exactly how useState is typed internally)


    function useState<T>(initial: T): [T, (val: T) => void] { ... }

    const [count, setCount] = useState(0);

Optional & Rest Elements


    // Optional element
    let rgb: [number, number, number, number?] = [255, 0, 128]; // alpha is optional

    // Rest elements (variable length tail)
    let log: [string, ...number[]] = ["scores", 90, 85, 78];

Named Tuple Elements (TS 4.0+)

You can label positions for better readability — labels are purely documentary and don't affect behavior:


    type Range = [start: number, end: number];

    function getRange(): Range {
        return [0, 100];
    }

Readonly Tuples

Prevent mutation with readonly:


    const point: readonly [number, number] = [10, 20];
    point[0] = 5; // ❌ Error: Cannot assign to '0' because it is a read-only

Tuples vs Arrays

Array

Tuple

Length

Variable

Fixed

Types

Homogeneous

Heterogeneous

Access

By index

By index

Use when

Same-type list

Small, ordered group

Key Gotcha

TypeScript doesn't enforce tuple length at runtime — it's a compile-time feature only. If you push to a tuple, TypeScript won't always catch it unless you use readonly.


    let pair: [string, number] = ["hello", 1];
    pair.push(999); // TS allows this — be careful!

    console.log(pair); // ["hello", 1, 999]

In short, use tuples when you have a small, ordered, fixed collection of values with different types and positional meaning matters.

Real-world use in Angular — the useState-style approach:


    // HTTP response often comes as [data, error]
    let apiResponse: [any, string | null] = [{ users: [] }, null];


Chapter 5 — Objects and Type Aliases

5.1 — Typing Objects Inline


    let user: { name: string; age: number; email: string } = {
        name: "Priya",
        age: 28,
        email: "priya@gmail.com"
    };

This works but it's messy. If you need this shape in multiple places, you'd have to repeat this big type annotation everywhere. This is where Type Aliases come in.


5.2 — Type Aliases

A type alias creates a reusable name for a type.


    type User = {
        name: string;
        age: number;
        email: string;
    };

    let user1: User = {
        name: "Priya",
        age: 28,
        email: "priya@gmail.com"
    };

    let user2: User = {
        name: "Rahul",
        age: 25,
        email: "rahul@gmail.com"
    };

    // Now you can reuse 'User' anywhere
    function printUser(user: User): void {
        console.log(`${user.name} (${user.age}) — ${user.email}`);
    }

    printUser(user1); // Priya (28) — priya@gmail.com
    printUser(user2); // Rahul (25) — rahul@gmail.com

5.3 — Optional Properties

Sometimes a property may or may not exist. Use ? to mark it as optional.


    type Product = {
        id: number;
        name: string;
        price: number;
        description?: string;    // optional — may or may not exist
        discount?: number;       // optional
    };

    // This is valid even without description and discount
    let laptop: Product = {
        id: 1,
        name: "Laptop",
        price: 55000
    };

    // This is also valid
    let phone: Product = {
        id: 2,
        name: "iPhone",
        price: 80000,
        description: "Latest model with great camera",
        discount: 5000
    };


5.4 — Readonly Properties

Sometimes you want a property that can be set once but never changed.


    type Config = {
        readonly apiUrl: string;
        readonly maxRetries: number;
        timeout: number;          // this one can change
    };

    let appConfig: Config = {
        apiUrl: "https://api.myapp.com",
        maxRetries: 3,
        timeout: 5000
    };

    appConfig.timeout = 10000;         // OK — timeout is not readonly
    appConfig.apiUrl = "http://other"; // ERROR: Cannot assign to 'apiUrl' because it is a read-only property


Chapter 6 — Interfaces — The Angular Developer's Best Friend

Interfaces are similar to Type Aliases but they work differently under the hood and are more powerful for object shapes.

Here's the key practical difference:

  • Type Aliases can describe any type: objects, unions, primitives, tuples
  • Interfaces are specifically for describing the shape of objects (and classes)

In Angular codebases, you'll see both. But interfaces are more common for defining data shapes that come from APIs.


6.1 — Basic Interface


    interface Product {
        id: number;
        title: string;
        price: number;
        category: string;
        inStock: boolean;
        image?: string;        // optional
    }

    let product1: Product = {
        id: 1,
        title: "MacBook Pro",
        price: 150000,
        category: "Electronics",
        inStock: true
    };

    let product2: Product = {
        id: 2,
        title: "iPhone 15",
        price: 80000,
        category: "Electronics",
        inStock: false,
        image: "iphone15.jpg"
    };


6.2 — Interface with Methods

An interface can also define methods that any implementing object must have.


    interface Vehicle {
        brand: string;
        speed: number;
        accelerate(amount: number): void;
        brake(amount: number): void;
        getInfo(): string;
    }


6.3 — Interface Extending Another Interface

This is where interfaces shine over type aliases — clean inheritance.


    interface Animal {
        name: string;
        age: number;
    }

    interface Dog extends Animal {
        breed: string;
        canFetch: boolean;
    }

    // Dog now has: name, age, breed, canFetch
    let myDog: Dog = {
        name: "Bruno",
        age: 3,
        breed: "Labrador",
        canFetch: true
    };

Real-world Angular example:


    interface BaseEntity {
        id: number;
        createdAt: string;
        updatedAt: string;
    }

    interface User extends BaseEntity {
        name: string;
        email: string;
        role: "admin" | "user" | "guest";
    }

    interface BlogPost extends BaseEntity {
        title: string;
        content: string;
        authorId: number;
        tags: string[];
    }

Now User and BlogPost both automatically have id, createdAt, and updatedAt because they extend BaseEntity. This is exactly the pattern you'll use when your API returns data.


6.4 — Interface vs Type — When to Use Which

For objects that represent data models (API responses, database entities): use Interface


    interface UserResponse {
        id: number;
        name: string;
        email: string;
    }

For unions, tuples, or complex type combinations: use Type


    type Status = "loading" | "success" | "error";
    type ID = string | number;
    type Pair = [string, number];


Chapter 7 — Union and Intersection Types

7.1 — Union Types — OR

A union type means a value can be one type OR another type.


    type StringOrNumber = string | number;

    let id: StringOrNumber = 5;
    id = "abc-123";      // also valid
    id = true;           // ERROR: boolean is not in the union

    // Practical example
    type Status = "active" | "inactive" | "banned";
    let userStatus: Status = "active";
    userStatus = "inactive";  // valid
    userStatus = "deleted";   // ERROR: "deleted" is not in the union

The "active" | "inactive" | "banned" is called a String Literal Union. This is extremely common in Angular. You're not just saying "any string" — you're saying "exactly one of these specific strings".


    // Without union — accepts ANY string
    function setStatus(status: string) { ... }
    setStatus("anything goes");   // no error even if wrong

    // With union — only specific strings allowed
    function setStatus(status: "active" | "inactive" | "banned") { ... }
    setStatus("active");    // valid
    setStatus("deleted");   // ERROR caught at compile time


7.2 — Intersection Types — AND

An intersection type combines multiple types into one. The result must have ALL properties of all combined types.


    type Timestamps = {
        createdAt: string;
        updatedAt: string;
    };

    type UserData = {
        id: number;
        name: string;
        email: string;
    };

    // UserWithTimestamps has ALL properties from both
    type UserWithTimestamps = UserData & Timestamps;

    let user: UserWithTimestamps = {
        id: 1,
        name: "Rahul",
        email: "rahul@gmail.com",
        createdAt: "2024-01-01",
        updatedAt: "2024-03-15"
    };


Chapter 8 — Functions — Deep Dive

Functions are where you'll spend most of your time in Angular. Let's cover everything.

8.1 — Function Parameters and Return Types


    // Basic function with typed params and return type
    function add(a: number, b: number): number {
        return a + b;
    }

    // Arrow function version
    const multiply = (a: number, b: number): number => a * b;

    // Function that returns nothing
    function logError(message: string): void {
        console.error(`[ERROR]: ${message}`);
    }

    // Function with optional parameter
    function greet(name: string, title?: string): string {
        if (title) {
            return `Hello, ${title} ${name}!`;
        }
        return `Hello, ${name}!`;
    }

    console.log(greet("Sharma"));             // Hello, Sharma!
    console.log(greet("Sharma", "Dr."));     // Hello, Dr. Sharma!


8.2 — Default Parameters


    function createUser(
        name: string,
        role: string = "user",        // default value
        isActive: boolean = true      // default value
    ): void {
        console.log(`Created: ${name}, Role: ${role}, Active: ${isActive}`);
    }

    createUser("Rahul");                     // Created: Rahul, Role: user, Active: true
    createUser("Priya", "admin");           // Created: Priya, Role: admin, Active: true
    createUser("Amit", "user", false);      // Created: Amit, Role: user, Active: false


8.3 — Rest Parameters

When you don't know how many arguments will be passed:


    function sum(...numbers: number[]): number {
        return numbers.reduce((total, n) => total + n, 0);
    }

    console.log(sum(1, 2, 3));             // 6
    console.log(sum(10, 20, 30, 40, 50)); // 150


8.4 — Function Types — Functions as Values

In Angular, you'll sometimes pass functions as arguments (callbacks, event handlers). You can type these too.


    // Define a type for a function
    type MathOperation = (a: number, b: number) => number;

    // Use that type
    let add: MathOperation = (a, b) => a + b;
    let subtract: MathOperation = (a, b) => a - b;
    let multiply: MathOperation = (a, b) => a * b;

    function calculate(a: number, b: number, operation: MathOperation): number {
        return operation(a, b);
    }

    console.log(calculate(10, 5, add));       // 15
    console.log(calculate(10, 5, subtract));  // 5
    console.log(calculate(10, 5, multiply));  // 50


8.5 — Async Functions

You'll use these constantly when fetching API data in Angular services.


    // A function that returns a Promise of a number
    async function fetchUserId(name: string): Promise<number> {
        // Simulating an API call delay
        await new Promise(resolve => setTimeout(resolve, 1000));
        return 42; // returning a user ID
    }

    // Calling it
    async function main() {
        const userId = await fetchUserId("Rahul");
        console.log("User ID:", userId); // User ID: 42
    }

    main();


Chapter 9 — Classes — The Heart of Angular

Every single Angular component, service, module, pipe, and guard is a class. Understanding TypeScript classes deeply means understanding Angular deeply.

9.1 — Basic Class Structure


    class BankAccount {
        // Properties
        accountNumber: string;
        holderName: string;
        balance: number;

        // Constructor — runs when you create a new instance
        constructor(accountNumber: string, holderName: string, initialBalance: number) {
            this.accountNumber = accountNumber;
            this.holderName = holderName;
            this.balance = initialBalance;
        }

        // Methods
        deposit(amount: number): void {
            if (amount <= 0) {
                console.log("Deposit amount must be positive");
                return;
            }
            this.balance += amount;
            console.log(`Deposited ₹${amount}. New balance: ₹${this.balance}`);
        }

        withdraw(amount: number): void {
            if (amount > this.balance) {
                console.log("Insufficient funds");
                return;
            }
            this.balance -= amount;
            console.log(`Withdrew ₹${amount}. New balance: ₹${this.balance}`);
        }

        getStatement(): string {
            return `Account: ${this.accountNumber} | Holder: ${this.holderName} | Balance: ₹${this.balance}`;
        }
    }

    // Create instances
    let account1 = new BankAccount("ACC001", "Rahul Sharma", 10000);
    let account2 = new BankAccount("ACC002", "Priya Patel", 25000);

    account1.deposit(5000);    // Deposited ₹5000. New balance: ₹15000
    account1.withdraw(2000);   // Withdrew ₹2000. New balance: ₹13000
    console.log(account1.getStatement());
    // Account: ACC001 | Holder: Rahul Sharma | Balance: ₹13000


9.2 — Access Modifiers — public, private, protected

This is critical for Angular because you'll use access modifiers in every component and service.


    class Employee {
        public name: string;           // accessible from anywhere
        public department: string;

        private salary: number;        // only accessible inside this class
        private ssn: string;           // sensitive data — keep private

        protected employeeId: string;  // accessible here and in subclasses

        constructor(
            name: string,
            department: string,
            salary: number,
            ssn: string,
            employeeId: string
        ) {
            this.name = name;
            this.department = department;
            this.salary = salary;
            this.ssn = ssn;
            this.employeeId = employeeId;
        }

        // A public method that safely exposes salary info
        getSalaryInfo(): string {
            return `${this.name} earns in the ${this.getSalaryRange()} range`;
        }

        // A private helper method — only used internally
        private getSalaryRange(): string {
            if (this.salary < 30000) return "junior";
            if (this.salary < 70000) return "mid-level";
            return "senior";
        }
    }

    let emp = new Employee("Amit", "Engineering", 65000, "XXX-XX-1234", "EMP001");
    console.log(emp.name);           // "Amit" — works, public
    console.log(emp.getSalaryInfo()); // works, public method
    console.log(emp.salary);         // ERROR: 'salary' is private


9.3 — Shorthand Constructor (Used Constantly in Angular)

TypeScript gives you a shorthand to declare and initialize properties directly in the constructor:


    // Long way:
    class User {
        name: string;
        email: string;
        role: string;

        constructor(name: string, email: string, role: string) {
            this.name = name;
            this.email = email;
            this.role = role;
        }
    }

    // Shorthand way (EXACT same result):
    class User {
        constructor(
            public name: string,
            public email: string,
            private role: string
        ) { }   // constructor body is empty — TypeScript handles it automatically
    }

You will see this shorthand in Angular services ALL the time:


    // Angular service injection shorthand
    constructor(
        private http: HttpClient,
        private router: Router,
        private authService: AuthService
    ) { }

That one line declares http, router, and authService as private properties AND injects them. This is the standard Angular pattern.


9.4 — Inheritance — extends


    class Animal {
        name: string;
        sound: string;

        constructor(name: string, sound: string) {
            this.name = name;
            this.sound = sound;
        }

        makeSound(): void {
            console.log(`${this.name} says ${this.sound}`);
        }

        describe(): string {
            return `I am ${this.name}`;
        }
    }

    class Dog extends Animal {
        breed: string;

        constructor(name: string, breed: string) {
            super(name, "Woof");   // super() calls the parent constructor
            this.breed = breed;
        }

        fetch(item: string): void {
            console.log(`${this.name} fetched the ${item}!`);
        }

        // Override parent method
        describe(): string {
            return `${super.describe()} and I am a ${this.breed}`;
        }
    }

    let dog = new Dog("Bruno", "Labrador");
    dog.makeSound();          // Bruno says Woof
    dog.fetch("ball");        // Bruno fetched the ball!
    console.log(dog.describe()); // I am Bruno and I am a Labrador


9.5 — Abstract Classes

An abstract class is a class that cannot be instantiated directly. It serves as a template for other classes.


    abstract class Shape {
        color: string;

        constructor(color: string) {
            this.color = color;
        }

        // Abstract method — subclasses MUST implement this
        abstract calculateArea(): number;

        // Regular method — subclasses inherit this as-is
        describe(): void {
            console.log(`This is a ${this.color} shape with area ${this.calculateArea()}`);
        }
    }

    class Circle extends Shape {
        radius: number;

        constructor(color: string, radius: number) {
            super(color);
            this.radius = radius;
        }

        calculateArea(): number {   // must implement abstract method
            return Math.PI * this.radius * this.radius;
        }
    }

    class Rectangle extends Shape {
        width: number;
        height: number;

        constructor(color: string, width: number, height: number) {
            super(color);
            this.width = width;
            this.height = height;
        }

        calculateArea(): number {   // must implement abstract method
            return this.width * this.height;
        }
    }

    let circle = new Circle("red", 5);
    circle.describe();  // This is a red shape with area 78.53...

    let rect = new Rectangle("blue", 10, 4);
    rect.describe();    // This is a blue shape with area 40

    // let shape = new Shape("green");  // ERROR: Cannot instantiate abstract class


Chapter 10 — Generics — Writing Flexible Reusable Code

Generics are one of the most powerful TypeScript features and they appear everywhere in Angular, especially with HTTP calls and RxJS.

10.1 — The Problem Generics Solve

Imagine you want to write a function that returns the first item of an array. Without generics:


    function getFirstItem(arr: string[]): string {
        return arr[0];
    }
    // Only works for string arrays. What about number arrays? object arrays?

You'd have to write the same function multiple times for each type. OR use any, which loses type safety.

With Generics:


    function getFirstItem<T>(arr: T[]): T {
        return arr[0];
    }

    // TypeScript infers the type automatically
    let firstFruit = getFirstItem(["apple", "mango", "banana"]);
    // TypeScript knows firstFruit is a string

    let firstScore = getFirstItem([95, 87, 72]);
    // TypeScript knows firstScore is a number

    let firstUser = getFirstItem([{ id: 1, name: "Rahul" }, { id: 2, name: "Priya" }]);
    // TypeScript knows firstUser is { id: number, name: string }

<T> is a type parameter. It's a placeholder for whatever type gets passed in. The name T is convention (stands for Type), but you can use any name.


10.2 — Generic Interfaces — The Angular HTTP Pattern

This is exactly how Angular's HTTP client works:


    // Generic API response wrapper
    interface ApiResponse<T> {
        data: T;
        status: number;
        message: string;
        timestamp: string;
    }

    // Specific interfaces for different data
    interface User {
        id: number;
        name: string;
        email: string;
    }

    interface Product {
        id: number;
        title: string;
        price: number;
    }

    // Now use the generic wrapper with specific types
    let userResponse: ApiResponse<User> = {
        data: { id: 1, name: "Rahul", email: "rahul@gmail.com" },
        status: 200,
        message: "Success",
        timestamp: "2024-03-28T10:00:00Z"
    };

    let productResponse: ApiResponse<Product> = {
        data: { id: 1, title: "Laptop", price: 55000 },
        status: 200,
        message: "Success",
        timestamp: "2024-03-28T10:00:00Z"
    };

    // ApiResponse<User[]> for a list of users
    let usersResponse: ApiResponse<User[]> = {
        data: [
            { id: 1, name: "Rahul", email: "rahul@gmail.com" },
            { id: 2, name: "Priya", email: "priya@gmail.com" }
        ],
        status: 200,
        message: "Success",
        timestamp: "2024-03-28T10:00:00Z"
    };

In Angular's HTTP client, you write this.http.get<User[]>(url). That <User[]> is a generic — you're telling TypeScript what shape the API response data will be.


10.3 — Generic Classes


    class Stack<T> {
        private items: T[] = [];

        push(item: T): void {
            this.items.push(item);
        }

        pop(): T | undefined {
            return this.items.pop();
        }

        peek(): T | undefined {
            return this.items[this.items.length - 1];
        }

        isEmpty(): boolean {
            return this.items.length === 0;
        }

        size(): number {
            return this.items.length;
        }
    }

    // A stack of numbers
    let numberStack = new Stack<number>();
    numberStack.push(10);
    numberStack.push(20);
    numberStack.push(30);
    console.log(numberStack.peek());  // 30
    console.log(numberStack.pop());   // 30
    console.log(numberStack.size());  // 2

    // A stack of strings — same class, different type
    let stringStack = new Stack<string>();
    stringStack.push("first");
    stringStack.push("second");
    console.log(stringStack.pop());   // "second"


Chapter 11 — Enums — Named Constants

Enums let you define a set of named constants. Instead of using magic strings or numbers throughout your code, you give them meaningful names.


    // Without enum — bad practice
    let userRole = "admin";      // What are the valid values? Hard to know.
    let orderStatus = 2;         // What does 2 mean? Hard to know.

    // With enum — clear and safe
    enum UserRole {
        Admin = "admin",
        User = "user",
        Guest = "guest",
        Moderator = "moderator"
    }

    enum OrderStatus {
        Pending = "PENDING",
        Processing = "PROCESSING",
        Shipped = "SHIPPED",
        Delivered = "DELIVERED",
        Cancelled = "CANCELLED"
    }

    // Using enums
    let currentUserRole: UserRole = UserRole.Admin;
    let currentOrderStatus: OrderStatus = OrderStatus.Processing;

    console.log(currentUserRole);   // "admin"
    console.log(currentOrderStatus); // "PROCESSING"

    // Excellent for switch statements
    function getStatusMessage(status: OrderStatus): string {
        switch (status) {
            case OrderStatus.Pending:
                return "Your order is waiting to be processed";
            case OrderStatus.Processing:
                return "Your order is being prepared";
            case OrderStatus.Shipped:
                return "Your order is on its way";
            case OrderStatus.Delivered:
                return "Your order has been delivered";
            case OrderStatus.Cancelled:
                return "Your order has been cancelled";
            default:
                return "Unknown status";
        }
    }

    console.log(getStatusMessage(OrderStatus.Shipped));
    // "Your order is on its way"


Chapter 12 — Type Guards and Narrowing

Type narrowing is the process of refining a type within a conditional block. TypeScript is smart enough to understand that after a type check, the type is more specific.


    type StringOrNumber = string | number;

    function processValue(value: StringOrNumber): string {
        // At this point, 'value' could be string or number

        if (typeof value === "string") {
            // Inside here, TypeScript KNOWS value is a string
            return value.toUpperCase();  // .toUpperCase() is valid
        } else {
            // Inside here, TypeScript KNOWS value is a number
            return value.toFixed(2);     // .toFixed() is valid
        }
    }

    console.log(processValue("hello"));  // "HELLO"
    console.log(processValue(42.567));   // "42.57"

With interfaces:


    interface Cat {
        name: string;
        meow(): void;
    }

    interface Dog {
        name: string;
        bark(): void;
    }

    type Pet = Cat | Dog;

    // Type guard function
    function isCat(pet: Pet): pet is Cat {
        return "meow" in pet;
    }

    function makeNoise(pet: Pet): void {
        if (isCat(pet)) {
            pet.meow();   // TypeScript knows it's a Cat here
        } else {
            pet.bark();   // TypeScript knows it's a Dog here
        }
    }


Chapter 13 — Decorators (Angular's Secret Sauce)

Decorators are a special TypeScript feature that Angular uses for absolutely everything. Let's understand them deeply.

A decorator is a function that is applied to a class, method, property, or parameter using the @ symbol. It adds metadata or modifies behavior.


13.1 — Class Decorator


    // This is a decorator factory — a function that returns a decorator
    function Component(config: { selector: string; template: string }) {
        return function (constructor: Function) {
            // Attach metadata to the class
            constructor.prototype.selector = config.selector;
            constructor.prototype.template = config.template;
            console.log(`Component registered: ${config.selector}`);
        }
    }

    // Using the decorator
    @Component({
        selector: 'app-header',
        template: '<header>My App</header>'
    })
    class HeaderComponent {
        // class code
    }
    // Console: "Component registered: app-header"

This is literally what Angular's @Component() decorator does — it takes your class and registers it as an Angular component with the selector and template metadata.


13.2 — Property Decorator


    function Log(target: any, propertyKey: string) {
        let value = target[propertyKey];

        Object.defineProperty(target, propertyKey, {
            get: () => {
                console.log(`Getting ${propertyKey}: ${value}`);
                return value;
            },
            set: (newValue) => {
                console.log(`Setting ${propertyKey} to: ${newValue}`);
                value = newValue;
            }
        });
    }

    class User {
        @Log
        name: string = "Rahul";
    }

    let user = new User();
    user.name = "Priya";   // Console: "Setting name to: Priya"
    console.log(user.name); // Console: "Getting name: Priya"

Angular's @Input() and @Output() work on this same principle — they're property decorators.


Chapter 14 — Modules and Imports/Exports

This is fundamental to how Angular is organized. Every Angular project is a set of TypeScript modules importing and exporting things from each other.


    // math-utils.ts
    export function add(a: number, b: number): number {
        return a + b;
    }

    export function subtract(a: number, b: number): number {
        return a - b;
    }

    export const PI = 3.14159265;

    // Default export — only one per file
    export default function multiply(a: number, b: number): number {
        return a * b;
    }


    // main.ts
    import multiply, { add, subtract, PI } from './math-utils';
    // multiply is the default export, add/subtract/PI are named exports

    console.log(add(5, 3));          // 8
    console.log(subtract(10, 4));    // 6
    console.log(PI);                 // 3.14159265
    console.log(multiply(3, 4));     // 12


    // Import everything as a namespace
    import * as MathUtils from './math-utils';
    console.log(MathUtils.add(2, 2));  // 4

In Angular, every component, service, module, and interface is exported from its file and imported where needed. This module system is how Angular organizes code.


Phase 1 Complete — You're TypeScript Ready


Let's recap everything we covered:

Type System — Why TypeScript exists, static type checking, catching bugs before runtime

Primitive Types — string, number, boolean, null, undefined, any, unknown, never — all 7 with real use cases

Type Inference — TypeScript figuring out types automatically

Arrays and Tuples — Typed arrays, array methods (map, filter, find, reduce), fixed-length tuples

Objects and Type Aliases — Inline object types, reusable type aliases, optional and readonly properties

Interfaces — Object blueprints, extending interfaces, interface vs type

Union and Intersection Types — OR types, AND types, string literal unions

Functions — Parameters, return types, optional params, default params, rest params, function types, async functions

Classes — Properties, constructors, methods, access modifiers (public/private/protected), shorthand constructors, inheritance with extends, super(), abstract classes

Generics — Generic functions, generic interfaces, generic classes — the foundation for Angular's HTTP calls

Enums — Named constants for roles, statuses, and categories

Type Guards — Narrowing types inside conditionals, custom type guard functions

Decorators — Class, property, and method decorators — exactly how Angular uses them

Modules — Export, import, default exports, named exports


One Final Practice Exercise Before Angular

Write this from scratch to test your understanding:

Build a simple Library system:

  1. Create an interface Book with id, title, author, year, genre, and optional rating
  2. Create an enum Genre with at least 5 genres
  3. Create a class Library with a private array of books
  4. Add methods: addBook, removeBook, findByAuthor, findByGenre, getTopRated
  5. The getTopRated method should accept a minimum rating and return all books above it
  6. Use proper TypeScript types everywhere — no any

If you can build this comfortably, you are ready for Angular Phase 2.

No comments:

Post a Comment

Phase 1 — Prerequisites & TypeScript Deep Dive to Learn Angular

Let's be very clear about one thing first. Most people jump straight into Angular and then struggle. They don't know why things work...