Interfaces
Interfaces define the shape of an object. They are heavily used in Angular for defining component inputs, service responses, and data models.
Basic Interface
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
}
const user: User = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
isActive: true,
};Optional and Readonly Properties
interface Product {
readonly id: number; // Cannot be changed after creation
name: string;
description?: string; // Optional
price: number;
tags?: string[]; // Optional
}
const product: Product = { id: 1, name: 'Widget', price: 29.99 };
product.name = 'Super Widget'; // OK
product.id = 2; // Error: readonlyExtending Interfaces
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
employeeId: string;
department: string;
}
interface Manager extends Employee {
directReports: Employee[];
}
const manager: Manager = {
name: 'Alice',
age: 35,
employeeId: 'E-001',
department: 'Engineering',
directReports: [],
};Interface for Functions
interface SearchFunction {
(query: string, limit?: number): Promise<string[]>;
}
const search: SearchFunction = async (query, limit = 10) => {
// implementation
return ['result1', 'result2'];
};Type Aliases vs Interfaces
Both can define object shapes, but they have differences:
// Type alias
type Point = {
x: number;
y: number;
};
// Interface
interface IPoint {
x: number;
y: number;
}| Feature | Interface | Type Alias |
|---|---|---|
| Extend/Implement | extends keyword | Intersection (&) |
| Declaration merging | ✅ Yes | ❌ No |
| Union types | ❌ No | ✅ Yes |
| Primitives/tuples | ❌ No | ✅ Yes |
| Use in Angular | Preferred for models | Preferred for unions |
When to Use What
// Use interface for object shapes (Angular services, models)
interface ApiResponse {
data: unknown;
status: number;
message: string;
}
// Use type for unions, intersections, primitives
type Theme = 'light' | 'dark' | 'system';
type Nullable<T> = T | null;
type Coordinate = [number, number];Generics
Generics let you write reusable code that works with any type while maintaining type safety.
Generic Functions
function identity<T>(value: T): T {
return value;
}
const str = identity<string>('hello'); // string
const num = identity<number>(42); // number
const auto = identity('inferred'); // string (inferred)Generic Interfaces
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: Date;
}
// Usage with different data types
type UserResponse = ApiResponse<User>;
type ProductListResponse = ApiResponse<Product[]>;
const response: ApiResponse<User> = {
data: { id: 1, name: 'John', email: 'j@test.com', isActive: true },
status: 200,
message: 'Success',
timestamp: new Date(),
};Generic Constraints
Limit what types can be used with a generic:
interface HasId {
id: number;
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
// Works with any type that has an 'id' property
const users: User[] = [/* ... */];
const found = findById(users, 1); // Type is User | undefinedMultiple Generic Parameters
function mapArray<T, U>(items: T[], transform: (item: T) => U): U[] {
return items.map(transform);
}
const names = mapArray([1, 2, 3], (n) => `Item ${n}`);
// Type: string[]Utility Types
TypeScript provides built-in utility types that transform existing types:
Partial<T>
Makes all properties optional:
interface User {
name: string;
email: string;
age: number;
}
// All properties become optional
function updateUser(id: number, updates: Partial<User>) {
// updates can have any combination of User properties
}
updateUser(1, { name: 'New Name' }); // OK
updateUser(1, { email: 'new@email.com' }); // OK
updateUser(1, {}); // OKPick<T, K> and Omit<T, K>
// Only selected properties
type UserPreview = Pick<User, 'name' | 'email'>;
// { name: string; email: string }
// All except specified properties
type UserWithoutAge = Omit<User, 'age'>;
// { name: string; email: string }Record<K, V>
Create an object type with specific key/value types:
type Role = 'admin' | 'editor' | 'viewer';
const permissions: Record<Role, string[]> = {
admin: ['read', 'write', 'delete'],
editor: ['read', 'write'],
viewer: ['read'],
};Required<T> and Readonly<T>
interface Config {
host?: string;
port?: number;
}
// All properties required
type RequiredConfig = Required<Config>;
// { host: string; port: number }
// All properties readonly
type FrozenConfig = Readonly<Config>;Common Angular Patterns
Component Input Types
interface TableColumn<T> {
key: keyof T;
header: string;
sortable?: boolean;
formatter?: (value: T[keyof T]) => string;
}
interface UserRow {
name: string;
email: string;
role: string;
}
const columns: TableColumn<UserRow>[] = [
{ key: 'name', header: 'Name', sortable: true },
{ key: 'email', header: 'Email' },
{ key: 'role', header: 'Role', formatter: (v) => String(v).toUpperCase() },
];Service Response Typing
interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
hasMore: boolean;
}
// Service method
class UserService {
getUsers(page: number): Observable<PaginatedResponse<User>> {
// ...
}
}Next Steps
With a solid understanding of types and interfaces, you're ready to learn about classes and decorators — the foundation of Angular's component and service architecture.