TypeScript Essentials
Types, interfaces, generics, decorators, utility types, strict mode
TypeScript is a typed superset of JavaScript that compiles to plain JS. Angular is built entirely in TypeScript and assumes proficiency with its type system. Understanding structural typing, generics, utility types, and decorators is prerequisite knowledge for productive Angular development.
Key Points
- TypeScript uses structural typing — types are compatible if their shapes match, not their names (duck typing with compiler checks)
- interface vs type alias: interfaces are open (can be merged/extended), type aliases are closed and support unions/intersections
- Generics: function<T>(arg: T): T — reusable, type-safe functions and classes; constraint with extends
- Utility types: Partial<T>, Required<T>, Readonly<T>, Pick<T,K>, Omit<T,K>, Record<K,V>, ReturnType<F>, Parameters<F>
- Union types (A | B) and intersection types (A & B) — discriminated unions with a common literal field for exhaustive switch
- Decorators: @Component, @Injectable, @Input — Angular's metadata system; class, method, accessor, property, parameter decorators
- strict mode: strictNullChecks, strictFunctionTypes, strictPropertyInitialization — essential for catching real bugs
- Type narrowing: typeof, instanceof, in, equality checks — TypeScript narrows the type within an if block
- as const: marks a literal as its exact type (readonly tuple/object) — useful for defining action types and config
TypeScript: discriminated union with exhaustive switch, generic constraints, utility types, as const, template literal types
// Discriminated union — exhaustive type narrowing
type Result<T> =
| { status: 'success'; data: T }
| { status: 'error'; message: string }
| { status: 'loading' };
function handle<T>(r: Result<T>): string {
switch (r.status) {
case 'success': return JSON.stringify(r.data);
case 'error': return r.message;
case 'loading': return 'Loading...';
// TypeScript errors if a case is missing (exhaustive check)
}
}
// Generic constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]; // 100% type-safe
}
// Utility types in practice
interface User { id: number; name: string; email: string; role: string; }
type UserSummary = Pick<User, 'id' | 'name'>;
type UserUpdate = Partial<Omit<User, 'id'>>; // all fields optional except id
type UserId = User['id']; // number — indexed access type
// as const — readonly tuple
const ROLES = ['admin', 'user', 'viewer'] as const;
type Role = typeof ROLES[number]; // 'admin' | 'user' | 'viewer'
// Template literal types (TS 4.1+)
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`; // 'onClick' | 'onFocus' | 'onBlur'
// Function overloads
function parse(input: string): number;
function parse(input: number): string;
function parse(input: string | number): number | string {
return typeof input === 'string' ? parseInt(input) : input.toString();
}Real-World Example
strictNullChecks catches ~30% of production JavaScript bugs at compile time — every null or undefined access becomes a compile error. TypeScript's structural typing lets you use Partial<User> as a DTO type without creating a separate interface — the compiler enforces which fields are required and which are optional at every call site.