Generics for Type-Safe Reuse
Generics let you write functions, classes, and interfaces that work with multiple types while preserving type information.
Generic Functions
function first<T>(items: T[]): T | undefined {
return items[0];
}
const num = first([1, 2, 3]); // number | undefined
const str = first(['a', 'b']); // string | undefined
Generic Interfaces
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
Generic Classes
class BehaviorSubject<T> {
private value: T;
constructor(initial: T) {
this.value = initial;
}
getValue(): T {
return this.value;
}
next(value: T): void {
this.value = value;
}
}
const subject = new BehaviorSubject<string>('hello');
subject.next('world');
const current: string = subject.getValue();
Generic Constraints
interface HasId {
id: number;
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
interface User extends HasId {
name: string;
}
const users: User[] = [{ id: 1, name: 'Alice' }];
const user = findById(users, 1); // User | undefined
Multiple Type Parameters
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
const entry = pair('name', 'Alice'); // [string, string]
Utility Types for Reshaping Types
TypeScript provides built-in utility types that transform existing types. These are heavily used in Angular for component inputs, form models, and API responses.
Common Utility Types
interface User {
id: number;
name: string;
email: string;
role: string;
createdAt: Date;
}
// Partial — all properties optional
type UserUpdate = Partial<User>;
// { id?: number; name?: string; ... }
// Pick — select specific properties
type UserPreview = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }
// Omit — exclude specific properties
type UserWithoutMeta = Omit<User, 'createdAt' | 'role'>;
// { id: number; name: string; email: string; }
// Record — map keys to values
type UserRoles = Record<string, 'admin' | 'editor' | 'viewer'>;
// { [key: string]: 'admin' | 'editor' | 'viewer' }
// Required — all properties required
type UserRequired = Required<UserUpdate>;
// Readonly — all properties readonly
type FrozenUser = Readonly<User>;
Utility Types in Angular Services
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users');
}
getUser(id: number): Observable<User> {
return this.http.get<User>(`/api/users/${id}`);
}
updateUser(id: number, changes: Partial<User>): Observable<User> {
return this.http.patch<User>(`/api/users/${id}`, changes);
}
createUser(data: Omit<User, 'id' | 'createdAt'>): Observable<User> {
return this.http.post<User>('/api/users', data);
}
}
Generic EventEmitter and Observable
@Component({
selector: 'app-user-list',
template: `...`,
})
export class UserListComponent {
@Output() userSelected = new EventEmitter<User>();
@Output() userDeleted = new EventEmitter<number>();
users$ = new BehaviorSubject<User[]>([]);
loading$ = new BehaviorSubject<boolean>(false);
}
Conditional Types
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // false
type NonNullable<T> = T extends null | undefined ? never : T;
type C = NonNullable<string | null>; // string
Quiz
1. What does the Partial<T> utility type do?
2. What does Pick<T, K> do?
3. How do you type an EventEmitter that emits a User object?
Flashcards
Question
What are generics in TypeScript?
Click to reveal answer
Answer
Type parameters that let functions, classes, and interfaces work with multiple types while preserving type information.
Question
What does Partial<User> create?
Click to reveal answer
Answer
A type where all User properties are optional. Useful for update operations.
Question
How do you type an Observable from an HTTP GET that returns User[]?
Click to reveal answer
Answer
this.http.get<User[]>('/api/users') — the generic parameter types the response body.
Revision Notes
Key Takeaways
- 1. Generics preserve type information across reusable code
- 2. Partial, Pick, and Omit reshape existing types
- 3. EventEmitter<T> and Observable<T> provide type-safe events
- 4. HTTP client methods use generics to type response bodies
Interview Tips
- • Explain how generics prevent 'any' from leaking through your codebase
- • Be ready to demonstrate utility types for common Angular patterns
- • Mention that generics are erased at runtime — they are compile-time only
Cheat Sheet
Generics and Utility Types Cheat Sheet
Generic function:
function first<T>(items: T[]): T | undefined {
return items[0];
}
Utility types:
Partial<T>— all optionalRequired<T>— all requiredPick<T, K>— select keysOmit<T, K>— exclude keysRecord<K, V>— key-value mapReadonly<T>— all readonly
Angular HTTP with generics:
this.http.get<User[]>('/api/users')
this.http.post<User>('/api/users', data)
this.http.patch<User>(`/api/users/${id}`, changes)