TypeScript for Angular
Angular is built with and for TypeScript — you cannot use Angular effectively without it. TypeScript is a statically-typed superset of JavaScript that compiles down to plain JavaScript.
This page covers the TypeScript features you will encounter most in Angular development. If you already know TypeScript well, use this as a quick reference. If you are new to TypeScript, work through each section carefully.
Why TypeScript in Angular?
Catch bugs at compile time rather than at runtime in the browser
Excellent IDE autocompletion and refactoring support
Decorators (@Component, @Injectable, @Input) — a core Angular mechanism
Interfaces and generics make component contracts explicit
Strict template type checking catches errors in HTML files
Self-documenting code — function signatures show what data they expect
Type Annotations
TypeScript adds type annotations to variables, parameters, and return values:
// Basic types
let name: string = 'Angular';
let version: number = 17;
let isStable: boolean = true;
let tags: string[] = ['framework', 'typescript'];
let tuple: [string, number] = ['Angular', 17];
// Union types — value can be one of several types
let id: string | number = 'abc';
id = 42; // also valid
// any — disables type checking (avoid in Angular)
let anything: any = 'hello';
anything = 42; // no error, but dangerous
// unknown — safer than any — you must check the type before using
let value: unknown = getData();
if (typeof value === 'string') {
console.log(value.toUpperCase()); // safe
}Interfaces
Interfaces define the shape of an object. Angular uses them extensively for component inputs, API responses, and service contracts:
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user' | 'guest'; // literal union type
createdAt: Date;
avatar?: string; // optional property (the ? makes it optional)
readonly apiKey: string; // cannot be changed after creation
}
// Using the interface
function greetUser(user: User): string {
return `Hello, ${user.name}! You are logged in as ${user.role}.`;
}
// TypeScript catches mistakes at compile time
const user: User = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
role: 'admin',
createdAt: new Date(),
apiKey: 'abc123',
};user.model.ts, product.model.ts. This keeps your type definitions centralized and reusable.Classes and Access Modifiers
Angular components and services are TypeScript classes. Access modifiers control visibility:
class ProductService {
// private — only accessible inside this class
private apiUrl = 'https://api.example.com/products';
// protected — accessible in this class and subclasses
protected cache = new Map<number, Product>();
// public (default) — accessible anywhere
public loading = false;
// readonly — can only be set once (in constructor or declaration)
readonly maxRetries = 3;
constructor(
// Shorthand: declare + initialize in one step
private http: HttpClient,
public authService: AuthService,
) {}
getProduct(id: number): Observable<Product> {
return this.http.get<Product>(`${this.apiUrl}/${id}`);
}
}Generics
Generics let you write reusable code that works with different types. Angular uses generics extensively in Observable<T>, EventEmitter<T>, and HttpClient.get<T>:
// Generic function
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const firstNumber = first([1, 2, 3]); // type: number
const firstString = first(['a', 'b']); // type: string
// Generic interface
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Usage in Angular
interface Product { id: number; name: string; price: number; }
// HttpClient.get<T> — the T tells TypeScript what the response shape is
this.http.get<ApiResponse<Product[]>>('/api/products')
.subscribe(response => {
// response.data is typed as Product[]
this.products = response.data;
});Decorators
Decorators are a TypeScript feature that Angular uses heavily. They are functions that modify classes, methods, properties, or parameters by attaching metadata.
Angular's most important decorators:
Decorator | Applied To | Purpose |
|---|---|---|
@Component | Class | Marks a class as an Angular component |
@Injectable | Class | Marks a class as injectable via DI |
@NgModule | Class | Marks a class as an NgModule |
@Directive | Class | Marks a class as a directive |
@Pipe | Class | Marks a class as a pipe |
@Input() | Property | Declares an input property (parent → child data) |
@Output() | Property | Declares an output property (child → parent events) |
@ViewChild() | Property | Gets a reference to a child component or DOM element |
@HostListener() | Method | Listens to events on the host element |
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
@Component({
selector: 'app-rating',
standalone: true,
template: `...`,
})
export class RatingComponent implements OnInit {
@Input() maxStars = 5;
@Input({ required: true }) initialRating!: number; // required input
@Output() ratingChange = new EventEmitter<number>();
currentRating = 0;
ngOnInit() {
this.currentRating = this.initialRating;
}
setRating(stars: number) {
this.currentRating = stars;
this.ratingChange.emit(stars);
}
}Enums
Enums are named constants — useful for values like status codes, user roles, and event types:
// Numeric enum (default)
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
// String enum (preferred — more readable in logs and debugging)
enum UserRole {
Admin = 'ADMIN',
Editor = 'EDITOR',
Viewer = 'VIEWER',
}
// Const enum — inlined at compile time (zero runtime overhead)
const enum Status {
Active = 'active',
Inactive = 'inactive',
Pending = 'pending',
}
// Usage in an Angular component
interface User {
name: string;
role: UserRole;
}
function canEdit(user: User): boolean {
return user.role === UserRole.Admin || user.role === UserRole.Editor;
}Type Aliases
// Type alias for a union type
type ID = string | number;
// Type alias for a function signature
type EventHandler<T> = (event: T) => void;
// Type alias for a complex object — similar to interface but can use unions
type Theme = 'light' | 'dark' | 'system';
type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';
// Mapped types — transform all properties
type Partial<T> = { [K in keyof T]?: T[K] }; // all properties optional
type Readonly<T> = { readonly [K in keyof T]: T[K] }; // all properties readonly
// Utility types Angular developers use often
type UserUpdate = Partial<User>; // all User fields optional
type ReadonlyUser = Readonly<User>; // all User fields readonly
type UserPreview = Pick<User, 'id' | 'name'>; // only id and name
type UserWithoutPassword = Omit<User, 'password'>; // everything except passwordOptional Chaining and Nullish Coalescing
These two operators are critical in Angular templates and services where data might be null or undefined:
const user = getUserById(1);
// Optional chaining (?.) — stops evaluation if value is null/undefined
const city = user?.address?.city; // undefined if user or address is null
const firstTag = user?.tags?.[0]; // safe array access
// Nullish coalescing (??) — returns right side if left is null/undefined
const displayName = user?.name ?? 'Anonymous';
const pageSize = config?.pageSize ?? 10;
// Combining both
const userRole = user?.profile?.role ?? 'guest';
// Non-null assertion (!) — tells TypeScript "trust me, this is not null"
// Use sparingly — only when you are 100% certain
const element = document.getElementById('root')!; // asserts not null?. built in: {{ user?.address?.city }}. This prevents template errors when data is loading.Async/Await and Promises
// Traditional Promise chain
fetch('/api/users')
.then(response => response.json())
.then(users => console.log(users))
.catch(error => console.error(error));
// Async/await — cleaner syntax
async function loadUsers(): Promise<User[]> {
try {
const response = await fetch('/api/users');
if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
return await response.json() as User[];
} catch (error) {
console.error('Failed to load users:', error);
return [];
}
}
// In Angular, you'll mostly use Observables (RxJS) rather than Promises
// But async/await is useful for one-time operations
async ngOnInit(): Promise<void> {
this.users = await this.userService.getUsersOnce();
}TypeScript with Angular Components — Putting It Together
import { Component, Input, Output, EventEmitter, signal, computed } from '@angular/core';
// Interfaces for the component's data
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
type SortField = 'name' | 'price' | 'quantity';
@Component({
selector: 'app-cart',
standalone: true,
template: `
<div>
<h2>Shopping Cart ({{ itemCount() }} items)</h2>
<p>Total: {{ total() | currency }}</p>
@for (item of items(); track item.id) {
<div>{{ item.name }} x{{ item.quantity }} = {{ item.price * item.quantity | currency }}</div>
}
</div>
`,
})
export class CartComponent {
// Signal-based reactive state
private cartItems = signal<CartItem[]>([]);
// Computed values derived from state
items = this.cartItems.asReadonly();
itemCount = computed(() => this.cartItems().reduce((sum, item) => sum + item.quantity, 0));
total = computed(() => this.cartItems().reduce((sum, item) => sum + item.price * item.quantity, 0));
// Typed input and output
@Input() set initialItems(items: CartItem[]) {
this.cartItems.set(items);
}
@Output() checkout = new EventEmitter<CartItem[]>();
addItem(item: CartItem): void {
this.cartItems.update(items => {
const existing = items.find(i => i.id === item.id);
if (existing) {
return items.map(i => i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i);
}
return [...items, { ...item, quantity: 1 }];
});
}
onCheckout(): void {
this.checkout.emit(this.cartItems());
}
}TypeScript Features Used Most in Angular
Feature | Angular Usage |
|---|---|
Interfaces | Model shapes (User, Product), @Input types |
Access modifiers (private/public) | Service methods, component properties |
Generics | Observable<T>, EventEmitter<T>, HttpClient.get<T> |
Decorators | @Component, @Injectable, @Input, @Output |
Optional chaining (?.) | Safe template expressions, API responses |
Nullish coalescing (??) | Default values when data may be absent |
Type aliases | Union types for literal values (role: admin | user) |
Enums | Constants for status, roles, event types |
Readonly | Immutable state signals, configuration objects |
async/await | One-time async operations, route resolvers |