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 content2.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.
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.tsexport 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 fileexport default function multiply(a: number, b: number): number {return a * b;}// main.tsimport multiply, { add, subtract, PI } from './math-utils';// multiply is the default export, add/subtract/PI are named exportsconsole.log(add(5, 3)); // 8console.log(subtract(10, 4)); // 6console.log(PI); // 3.14159265console.log(multiply(3, 4)); // 12// Import everything as a namespaceimport * 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:
- Create an interface
Bookwith id, title, author, year, genre, and optional rating - Create an enum
Genrewith at least 5 genres - Create a class
Librarywith a private array of books - Add methods:
addBook,removeBook,findByAuthor,findByGenre,getTopRated - The
getTopRatedmethod should accept a minimum rating and return all books above it - 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