TypeScript Programming Labs

Master advanced TypeScript concepts: modules, async/await, and decorators for production-ready applications.

Advanced Concepts - Module 4

Learn modules & namespaces, async/await patterns, and decorators for real-world TypeScript.

Lab 10: Modules & Namespaces
Advanced
Coding Challenge
Your Task: Learn how TypeScript organizes code using modules and namespaces. Modules are the modern way to structure code, while namespaces are useful for organizing within a single file.

Detailed Requirements:
1. Export functions and classes: Use export to make code available to other files:
// Named exports export function add(a: number, b: number): number { return a + b; } export const PI = 3.14159; export class Calculator { multiply(a: number, b: number): number { return a * b; } }

2. Default exports: Each module can have one default export:
// Default export (one per file) export default class MathUtils { static square(n: number): number { return n * n; } }

3. Import syntax: Import what you need from other modules:
// Named imports import { add, PI, Calculator } from './math'; // Default import (any name works) import MathUtils from './math'; // Import all as namespace import * as Math from './math'; // Renaming imports import { add as sum } from './math';

4. Create a namespace: Namespaces group related code together (useful in single files):
namespace Validation { export interface StringValidator { isValid(s: string): boolean; } export class EmailValidator implements StringValidator { isValid(s: string): boolean { return s.includes("@"); } } export class PhoneValidator implements StringValidator { isValid(s: string): boolean { return /^\d{10}$/.test(s); } } } // Usage const emailValidator = new Validation.EmailValidator(); console.log(emailValidator.isValid("test@email.com"));

5. Re-export pattern: Create barrel files that re-export from multiple modules:
// index.ts (barrel file) export { add, subtract } from './arithmetic'; export { Calculator } from './calculator'; export * from './constants';

Why Modules?
• Encapsulation - hide implementation details
• Reusability - import only what you need
• Maintainability - clear dependencies
• Tree-shaking - bundlers remove unused code

Expected Output:
add(5, 3) = 8 PI = 3.14159 Calculator: 4 * 7 = 28 MathUtils.square(5) = 25 Email valid: true Phone valid: true

Requirements Checklist

Create exported function (export function)
Create exported class with export keyword
Create exported constant (export const)
Create a namespace with exported members
Use namespace members (Namespace.Member)
Test all exports with console.log
Output
// Click "Run Code" to compile and execute
Hints & Tips
• Export function: export function add(a: number, b: number): number { }
• Export class: export class Calculator { }
• Export const: export const PI = 3.14159;
• Namespace: namespace Name { export class X { } }
• Use namespace: new Validation.EmailValidator()
Progress: 0/6
Score: 0/100
0%

Lab Results

Review feedback below

Lab 11: Async/Await & Promises
Advanced
Coding Challenge
Your Task: Master asynchronous TypeScript with Promises and async/await. This is essential for API calls, file operations, and any I/O-bound work.

Detailed Requirements:
1. Create a typed Promise: Promises in TypeScript use generics to specify the resolved type:
function fetchUser(id: number): Promise<User> { return new Promise((resolve, reject) => { setTimeout(() => { if (id > 0) { resolve({ id, name: "Alice", email: "alice@test.com" }); } else { reject(new Error("Invalid ID")); } }, 1000); }); }

2. Use async/await: Async functions always return a Promise. Await pauses execution until the Promise resolves:
async function getUser(id: number): Promise<User> { const user = await fetchUser(id); return user; } // The return type is inferred as Promise<User>

3. Handle errors with try/catch: Always handle potential rejections:
async function safeGetUser(id: number): Promise<User | null> { try { const user = await fetchUser(id); return user; } catch (error) { console.error("Failed to fetch user:", error); return null; } }

4. Promise.all for parallel execution: Run multiple async operations simultaneously:
async function getAllUsers(ids: number[]): Promise<User[]> { const promises = ids.map(id => fetchUser(id)); const users = await Promise.all(promises); return users; }

5. Promise.race: Returns when the first Promise settles:
async function fetchWithTimeout<T>( promise: Promise<T>, ms: number ): Promise<T> { const timeout = new Promise<never>((_, reject) => { setTimeout(() => reject(new Error("Timeout")), ms); }); return Promise.race([promise, timeout]); }

6. Async iteration: Process items one at a time:
async function processSequentially(ids: number[]): Promise<void> { for (const id of ids) { const user = await fetchUser(id); console.log("Processed:", user.name); } }

Expected Output:
Fetching user 1... User fetched: { id: 1, name: "Alice" } Parallel fetch complete: 3 users Sequential processing done Error handled gracefully

Requirements Checklist

Create a function returning Promise<T>
Create an async function using await
Use try/catch for error handling
Use Promise.all for parallel execution
Create timeout/race pattern (optional)
Test async operations with console.log
Output
// Click "Run Code" to compile and execute
Hints & Tips
• Promise type: function fn(): Promise<User> { return new Promise(...) }
• Async function: async function fn(): Promise<User> { }
• Await: const user = await fetchUser(1);
• Error handling: try { await fn() } catch (e) { }
• Parallel: await Promise.all([p1, p2, p3])
Progress: 0/6
Score: 0/100
0%

Lab Results

Review feedback below

Lab 12: Decorators
Expert
Coding Challenge
Your Task: Learn TypeScript decorators - a powerful way to add metadata and modify classes, methods, and properties. Decorators are used heavily in frameworks like Angular and NestJS.

Note: Decorators require "experimentalDecorators": true in tsconfig.json.

Detailed Requirements:
1. Class Decorator: A function that receives the constructor:
function Logger(constructor: Function) { console.log("Logging class:", constructor.name); } @Logger class Person { constructor(public name: string) {} } // Logs: "Logging class: Person" when class is defined

2. Decorator Factory: A function that returns a decorator (allows parameters):
function LoggerWithPrefix(prefix: string) { return function(constructor: Function) { console.log(prefix + constructor.name); }; } @LoggerWithPrefix("CLASS: ") class Animal { }

3. Method Decorator: Modify method behavior:
function Log( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { console.log("Calling " + propertyKey + " with:", args); const result = originalMethod.apply(this, args); console.log("Result:", result); return result; }; } class Calculator { @Log add(a: number, b: number): number { return a + b; } }

4. Property Decorator: Add metadata to properties:
function Required(target: any, propertyKey: string) { let value: any; Object.defineProperty(target, propertyKey, { get() { return value; }, set(newValue) { if (newValue === undefined || newValue === null) { throw new Error(propertyKey + " is required"); } value = newValue; } }); }

5. Parameter Decorator: Mark parameters for validation:
function ValidateParam( target: any, propertyKey: string, parameterIndex: number ) { console.log("Parameter " + parameterIndex + " of " + propertyKey + " needs validation"); }

6. Practical example - Timing decorator:
function Measure( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const original = descriptor.value; descriptor.value = function(...args: any[]) { const start = performance.now(); const result = original.apply(this, args); const end = performance.now(); console.log(propertyKey + " took " + (end - start) + "ms"); return result; }; }

Expected Output:
Class decorated: Person Calling add with: [5, 3] Result: 8 Method took 0.5ms Property validated

Requirements Checklist

Create a class decorator function
Create a decorator factory with parameters
Create a method decorator
Apply decorators with @ syntax
Create a timing/logging decorator
Test decorated classes with console.log
Output
// Click "Run Code" to compile and execute
Hints & Tips
• Class decorator: function Dec(constructor: Function) { }
• Factory: function Dec(arg) { return function(constructor) { } }
• Method decorator: function Dec(target, key, descriptor) { }
• Apply: @Decorator class MyClass { }
• Wrap method: descriptor.value = function(...args) { original.apply(this, args) }
Progress: 0/6
Score: 0/100
0%

Lab Results

Review feedback below

Congratulations!

You've completed all 12 TypeScript Programming Labs! You now have a solid foundation in TypeScript's type system, OOP features, and advanced concepts.