Join our FREE personalized newsletter for news, trends, and insights that matter to everyone in America

Newsletter
New

Typescript Utility Types Every Angular Developer Should Know

Card image cap

Introduction

TypeScript ships with a set of built-in generic types called utility types. They transform existing types into new ones — making properties optional, readonly, or required; picking or omitting specific keys; extracting return types from functions. They let you express complex type relationships without duplicating type definitions.

Most Angular developers use a handful of them — Partial and Observable come to mind — but the full set is underused relative to how much it can simplify type-heavy Angular code. This article covers the utility types that appear most frequently in Angular applications, with concrete examples drawn from the patterns you encounter every day: form handling, HTTP services, component inputs, NgRx state, and API response modeling.

Partial

Partial<T> makes all properties of T optional. Every property becomes T[key] | undefined.

The most common Angular use case is update payloads. When patching a resource, you only send the fields that changed — not the entire object.

interface User {  
  id: string;  
  name: string;  
  email: string;  
  role: 'admin' | 'viewer';  
  lastLogin: Date;  
}  
  
@Injectable({ providedIn: 'root' })  
export class UserService {  
  updateUser(id: string, changes: Partial<User>): Observable<User> {  
    return this.http.patch<User>(`/api/users/${id}`, changes);  
  }  
}  
  
// Caller only provides what changed — TypeScript enforces this  
this.userService.updateUser(userId, { name: 'Jane', role: 'admin' });  

Partial also appears in form state modeling. A form in progress may not have all fields populated yet:

interface ProductForm {  
  name: string;  
  price: number;  
  description: "string;"  
  category: string;  
}  
  
// Form state before the user has filled everything in  
type DraftProduct = Partial<ProductForm>;  
  
// TypeScript won't complain about missing fields  
const draft: DraftProduct = { name: 'Widget' };  

Required

Required<T> is the inverse of Partial — it makes all optional properties required. This is useful when you've modeled something with optional fields during construction but need to assert that all fields are present at a later stage.

interface Config {  
  apiUrl?: string;  
  timeout?: number;  
  retries?: number;  
}  
  
function validateConfig(config: Config): Required<Config> {  
  if (!config.apiUrl) throw new Error('apiUrl is required');  
  if (!config.timeout) throw new Error('timeout is required');  
  if (!config.retries) throw new Error('retries is required');  
  
  // After validation, we assert the config is fully populated  
  return config as Required<Config>;  
}  
  
// From this point on, TypeScript knows all fields are defined  
const validated = validateConfig(rawConfig);  
console.log(validated.apiUrl); // string, not string | undefined  

Readonly

Readonly<T> makes all properties of T non-writable. Attempts to mutate properties on a Readonly<T> type fail at compile time.

In Angular applications with OnPush change detection, immutability is the contract that makes the strategy work correctly. Readonly<T> lets you enforce this at the type level rather than just by convention:

interface AppState {  
  users: User[];  
  selectedUserId: string | null;  
  isLoading: boolean;  
}  
  
// State is never mutated — only replaced  
type ImmutableState = Readonly<AppState>;  
  
function reducer(state: ImmutableState, action: Action): ImmutableState {  
  switch (action.type) {  
    case 'SET_LOADING':  
      // Return new object — cannot mutate state directly  
      return { ...state, isLoading: action.payload };  
    default:  
      return state;  
  }  
}  

For deeply nested immutability, Readonly<T> only applies at the top level — nested objects remain mutable. For deep immutability, use ReadonlyArray<T> for arrays and recursive Readonly wrappers, or a library like immer.

Pick

Pick<T, K> creates a new type containing only the properties of T whose keys are in the union K.

The most valuable Angular use case is creating view models from full entity types. Your API returns a rich User object, but a particular component only needs three fields:

interface User {  
  id: string;  
  name: string;  
  email: string;  
  role: 'admin' | 'viewer';  
  createdAt: Date;  
  lastLogin: Date;  
  preferences: UserPreferences;  
  address: Address;  
}  
  
// The user card only needs these three fields  
type UserCardViewModel = Pick<User, 'id' | 'name' | 'email'>;  
  
@Component({  
  selector: 'app-user-card',  
  changeDetection: ChangeDetectionStrategy.OnPush  
})  
export class UserCardComponent {  
  @Input() user!: UserCardViewModel;  
  // Component can't accidentally access fields it shouldn't need  
}  

This pattern enforces component encapsulation at the type level. A component that displays a user card has no business accessing preferences or address. Pick makes that boundary explicit and compiler-enforced.

Omit

Omit<T, K> is the complement of Pick — it creates a type containing all properties of T except those in K.

The canonical Angular use case is create payloads. When creating a new resource, server-generated fields like id and createdAt don't exist yet:

interface User {  
  id: string;  
  name: string;  
  email: string;  
  role: 'admin' | 'viewer';  
  createdAt: Date;  
}  
  
// For creation, id and createdAt are server-generated  
type CreateUserPayload = Omit<User, 'id' | 'createdAt'>;  
  
@Injectable({ providedIn: 'root' })  
export class UserService {  
  createUser(payload: CreateUserPayload): Observable<User> {  
    return this.http.post<User>('/api/users', payload);  
  }  
}  
  
// TypeScript error if you try to pass id in the payload  
this.userService.createUser({  
  name: 'Jane',  
  email: 'jane@example.com',  
  role: 'viewer'  
  // id: '...' — TypeScript error: Object literal may only specify known properties  
});  

Record

Record<K, V> creates a type representing an object whose keys are of type K and whose values are of type V. It's the typed alternative to { [key: string]: V }.

In Angular applications, Record is useful for lookup tables, state maps, and any dictionary-style structure:

type UserId = string;  
type LoadingState = 'idle' | 'loading' | 'success' | 'error';  
  
// Track loading state per entity ID  
type EntityLoadingMap = Record<UserId, LoadingState>;  
  
// In an NgRx reducer or component state  
interface UserState {  
  entities: Record<UserId, User>;  
  loadingStates: Record<UserId, LoadingState>;  
  selectedId: UserId | null;  
}  

Record with a union key type is particularly powerful — it constrains the valid keys to only the members of the union:

type Route = 'home' | 'products' | 'orders' | 'settings';  
type RoutePermissions = Record<Route, boolean>;  
  
const permissions: RoutePermissions = {  
  home: true,  
  products: true,  
  orders: false,  
  settings: false,  
  // TypeScript error if any Route key is missing  
  // TypeScript error if any key outside Route is added  
};  

ReturnType

ReturnType<T> extracts the return type of a function type T. This is most useful when you want to derive a type from a function's output without declaring the type separately.

In Angular, this pattern appears most clearly with factory functions and NgRx selectors:

// A factory function that builds a complex object  
function createUserViewModel(user: User, permissions: Permissions) {  
  return {  
    id: user.id,  
    displayName: `${user.name} (${user.role})`,  
    canEdit: permissions.includes('user:edit'),  
    canDelete: permissions.includes('user:delete'),  
  };  
}  
  
// Derive the type from the function without duplicating it  
type UserViewModel = ReturnType<typeof createUserViewModel>;  
  
// Now use it as an @Input type  
@Component({ ... })  
export class UserDetailComponent {  
  @Input() viewModel!: UserViewModel;  
}  

With NgRx selectors:

export const selectUserViewModel = createSelector(  
  selectCurrentUser,  
  selectPermissions,  
  (user, permissions) => ({  
    id: user.id,  
    displayName: user.name,  
    canEdit: permissions.canEditUsers,  
  })  
);  
  
// Derive the output type from the selector  
type UserViewModelState = ReturnType<typeof selectUserViewModel>;  

Parameters

Parameters<T> extracts the parameter types of a function as a tuple. Less commonly used than ReturnType, but valuable when you need to pass or store function arguments as typed values.

function searchUsers(  
  query: string,  
  filters: UserFilters,  
  pagination: PaginationOptions  
): Observable<PaginatedResult<User>> {  
  return this.http.get<PaginatedResult<User>>('/api/users', {  
    params: buildQueryParams(query, filters, pagination)  
  });  
}  
  
// Extract parameter types without re-declaring them  
type SearchParams = Parameters<typeof searchUsers>;  
// SearchParams is [string, UserFilters, PaginationOptions]  
  
// Useful for caching search arguments  
function cacheSearch(params: SearchParams): void {  
  const [query, filters, pagination] = params;  
  // ...  
}  

NonNullable

NonNullable<T> removes null and undefined from a type union. Useful after null checks when you need to assert to TypeScript that a value is definitely present:

interface RouteData {  
  userId: string | null;  
  productId: string | undefined;  
}  
  
function processRoute(data: RouteData) {  
  if (!data.userId) throw new Error('userId required');  
  
  // Without NonNullable, TypeScript still thinks userId could be null here  
  // because it doesn't narrow across function calls  
  const userId: NonNullable<typeof data.userId> = data.userId;  
  // userId is now typed as string  
}  

A more practical Angular pattern — using NonNullable with the async pipe and strict null checks:

@Component({  
  template: `  
    @if (user$ | async; as user) {  
      <!-- Inside this block, user is NonNullable -->  
      <app-user-card [user]="user" />  
    }  
  `  
})  
export class UserPageComponent {  
  user$: Observable<User | null> = this.store.select(selectCurrentUser);  
}  

Extract and Exclude

Extract<T, U> extracts from T the types that are assignable to U. Exclude<T, U> does the opposite — it removes from T the types assignable to U.

These are most useful with union types:

type Status = 'idle' | 'loading' | 'success' | 'error';  
  
// Only the terminal states (not loading or idle)  
type TerminalStatus = Extract<Status, 'success' | 'error'>;  
// TerminalStatus = 'success' | 'error'  
  
// Everything except loading  
type NonLoadingStatus = Exclude<Status, 'loading'>;  
// NonLoadingStatus = 'idle' | 'success' | 'error'  

In Angular applications, this pattern is valuable for narrowing action types in effects or reducers:

type UserAction =  
  | { type: 'LOAD_USER'; id: string }  
  | { type: 'LOAD_USER_SUCCESS'; user: User }  
  | { type: 'LOAD_USER_FAILURE'; error: string }  
  | { type: 'UPDATE_USER'; changes: Partial<User> }  
  | { type: 'DELETE_USER'; id: string };  
  
// Only the actions that affect loading state  
type LoadingActions = Extract<  
  UserAction,  
  { type: 'LOAD_USER' | 'LOAD_USER_SUCCESS' | 'LOAD_USER_FAILURE' }  
>;  

Combining Utility Types

The real power comes from composing utility types. A few patterns that appear frequently in production Angular codebases:

Create vs Update payloads from a single interface:

interface Product {  
  id: string;  
  name: string;  
  price: number;  
  categoryId: string;  
  createdAt: Date;  
  updatedAt: Date;  
}  
  
type CreateProductPayload = Omit<Product, 'id' | 'createdAt' | 'updatedAt'>;  
type UpdateProductPayload = Partial<Omit<Product, 'id' | 'createdAt' | 'updatedAt'>>;  

Safe subset for a component input:

// Only expose the fields a display component needs, as readonly  
type ProductCardInput = Readonly<Pick<Product, 'id' | 'name' | 'price'>>;  

Discriminated union response type:

type ApiResponse<T> =  
  | { status: 'success'; data: T }  
  | { status: 'error'; message: string }  
  | { status: 'loading' };  
  
type SuccessResponse<T> = Extract<ApiResponse<T>, { status: 'success' }>;  
// SuccessResponse<T> = { status: 'success'; data: T }  

Conclusion

TypeScript's built-in utility types are the vocabulary of type-level programming in Angular. They let you express the relationships between your types — between a full entity and its create payload, between a rich API response and the view model a component needs, between a union of all possible states and the subset a particular handler cares about — without duplicating type definitions.

The individual types are each simple. The discipline of reaching for them consistently — instead of writing any, loosening types, or duplicating interfaces — is what makes a large TypeScript codebase maintainable. Each utility type usage is a constraint you're encoding once and getting for free everywhere the type is used.