TypeScript Prerequisites for Angular
The TypeScript you need before Angular makes sense, from interfaces and generics to the utility types v22 code leans on
⚡ What changed since the original
This article was first written for the TypeScript 3.x era (2020), when Angular 9 required TS 3.8+. The fundamentals — static types, interfaces, classes, generics, union/intersection/conditional types, utility types — are unchanged. What's new since then:
- TypeScript baseline for Angular v22 is TS 5.5+ (much stricter type inference, better error messages, faster builds). Versions earlier than 5.5 are no longer supported by the Angular compiler.
- The
satisfiesoperator (TS 4.9) is now common in Angular configs and route declarations — a short introduction below. - Signal-aware utility types like
Awaited<T>(TS 4.5+) andNoInfer<T>(TS 5.4+) are worth knowing for v22 patterns. - The Angular-specific code example updates
@Input() flexDirection: FlexDirection = 'row'to the v22 signal input form withinput()and thehost: {}object instead of@HostBinding. - Reference links updated from
angular.iotoangular.dev(Angular's documentation moved domains in v17).
No mechanism reflection — TypeScript primer content is largely timeless. The closing "Angular + TypeScript today" sidebar covers the relationship evolution briefly.
See also: Reactive Forms (typed forms in v22) · Signal Inputs · Dependency Injection
Over the first ten days we explored Angular concepts and wrote code — all of it TypeScript (TS). Working with Angular means TS fundamentals matter. Today we'll cover the basics; advanced types come in the second half of this article.
Solid TS foundations help when you tackle harder Angular topics like Dependency Injection.
What is TypeScript?
TS is a superset of JavaScript (JS). Installing TS gives you the TypeScript Compiler (tsc) CLI. tsc compiles TS to JS for browsers or runtimes like Node.js.

TS = JS plus extra features — mainly static types.
TypeScript minus the extras
TS adds static types. JS is dynamically typed and very permissive:
let john = 'John';john = 123;That's valid JS but error-prone at scale.
The same code in TS: john is inferred as string when assigned "John". Assigning 123 is a compilation-time error because number isn't assignable to string.
Below, infer means TypeScript's automatic type inference.

That's a compilation-time error. Your editor surfaces it while you type thanks to the language service; tsc reports the same when you build.
Default types
let someString: string;let someNumber: number;let someBoolean: boolean;let something: any; // assignable to any other typelet someStringArray: string[]; // likewise number[], boolean[], any[]let someObject: object;let someNull: null;let someUndefined: undefined;let someUnknown: unknown;let someNever: never; // e.g. a function that always throwslet someTuple: [string, number];let someVoidFunction: () => void;let someFunction: () => string;Interface / Type
Define object shapes with interface or type:
interface User { firstName: string; lastName: string; age: number; job?: string;}
type User = { firstName: string; lastName: string; age: number; job?: string;};
const john: User = { firstName: 'John', lastName: 'Doe', age: 20, job: 'Student',};const susan: User = { firstName: 'Sue', lastName: 'Smith', age: 40,};Think of an interface as a mold: job?: string is optional; other fields are required. Editors autocomplete john. with firstName, lastName, age, job.
interface and type overlap in many cases — pick one style per project. See interface vs type.
Class
class syntax (ES2015+) is syntactic sugar over prototypal inheritance in JS. TS adds strong typing and access modifiers.
class User { firstName: string; lastName: string; age: number; job?: string;
constructor(firstName: string, lastName: string, age: number, job?: string) { this.firstName = firstName; this.lastName = lastName; this.age = age; this.job = job; }}Shorthand with access modifiers:
class User { constructor( public firstName: string, public lastName: string, public age: number, public job?: string ) {}}Class vs interface: classes vs interfaces in TypeScript
Generics
abstract class BaseService<T> { protected model: Model<T>;
find(): T[] { return this.model.findAll(); }
findOne(id: number): T { return this.model.findById(id); }}
class DogService extends BaseService<Dog> { constructor(dogModel: Model<Dog>) { super(); this.model = dogModel; }}
class CatService extends BaseService<Cat> { constructor(catModel: Model<Cat>) { super(); this.model = catModel; }}<T> is a type parameter. DogService inherits find() / findOne() typed as Dog without rewriting them.
Why generics matter in v22 Angular: the entire forms system uses them
heavily — FormGroup<TControls>, FormControl<T>, FormArray<TControl>.
Signal inputs use InputSignal<T>. The Router uses Routes arrays with
typed data and resolve shapes. Comfort with generics pays off
immediately. See Reactive Forms for the
typed-forms deep dive.
Why TypeScript?
Pros
Explicit types, familiar OOP syntax (abstract, class, generics), and strong editor support help you build maintainable apps. TS consistently ranks among the most-loved languages in developer surveys, including the Stack Overflow Developer Survey.
Cons
More boilerplate, stricter checks, third-party libraries need .d.ts typings, and the so-called TypeScript tax.
Trade-off
Angular chose TS for structure, maintainability, and scale — worth the trade-offs for most teams. As of v22, Angular requires TypeScript — there's no untyped path.
Advanced types
TypeScript's type system is deep; a few articles can't cover it all. Practice and read the handbook.
Union type
Either/or types use |:
function listen(port: unknown) { if (typeof port === 'string') { port = parseInt(port, 10); } server.listen(port);}typeof
typeof 'string'; // stringtypeof 123; // numbertypeof true; // booleantypeof {}; // objecttypeof []; // objecttypeof (() => {}); // functiontypeof null; // objecttypeof undefined; // undefinedunknown (TS 3.0+) is safer than any when the type isn't known yet. See the unknown top type.
Narrow with a union:
function listen(port: string | number) { // do listen}
listen('3000'); // oklisten(3000); // oklisten(true); // error: not string | numberlisten(); // error: expected 1 argumentReturn types can be unions too. Reuse with a type alias:
type StringOrNumber = string | number;Intersection type
Combine types with & (and):
function merge<T1, T2>(o1: T1, o2: T2): T1 & T2 { return { ...o1, ...o2 };}Common in UI libraries — shared StyleProps intersected with component-specific props (type composition).
Conditional type
Since TS 2.8:
T extends U ? X : Y;If T is assignable to U, result is X; otherwise Y.
The satisfies operator (TS 4.9, common in v22 Angular)
satisfies lets you check that a value conforms to a type without widening or losing the literal types of the value. It's the answer to a frequent source of friction with type annotations:
// Without satisfies — the annotation widens the typeconst routes: Routes = [ { path: '', component: HomeComponent }, { path: 'about', component: AboutComponent },];// routes[0].path is string, not the literal '' — we've lost precision// With satisfies — type-checked, but literal types preservedconst routes = [ { path: '', component: HomeComponent }, { path: 'about', component: AboutComponent },] satisfies Routes;// routes[0].path is the literal '' — useful for downstream inferenceYou'll see satisfies in v22 Angular code most often around:
- Route configs — preserves literal path strings for typed link helpers
ApplicationConfig— preserves the precise shape ofproviders@Componentmetadata in third-party libraries that derive types from it- Discriminated unions where you want exhaustiveness checking without losing the variant tag
Rule of thumb: when you want a value to match a type but keep its full literal precision, reach for satisfies instead of : T.
Type alias examples
Restrict flexDirection to valid CSS values. The Angular 9 version used the decorator-based @Input() and @HostBinding(); the v22 version uses signal inputs and the host: {} object.
// Angular 9: decorator input + @HostBindingtype FlexDirection = 'row' | 'column' | 'row-reverse' | 'column-reverse';
@Component({ selector: 'flex-container', template: `<ng-content></ng-content>`})export class FlexComponent { @Input() flexDirection: FlexDirection = 'row';
@HostBinding('style.display') display = 'flex'; @HostBinding('style.flex-direction') get fd() { return this.flexDirection; }}// ── v22 equivalent: signal input + host object ────────────────────────────import { Component, input } from '@angular/core';
type FlexDirection = 'row' | 'column' | 'row-reverse' | 'column-reverse';
@Component({ selector: 'flex-container', template: `<ng-content />`, host: { 'style.display': 'flex', '[style.flex-direction]': 'flexDirection()', },})export class FlexComponent { readonly flexDirection = input<FlexDirection>('row');}The FlexDirection type alias is unchanged — that's pure TypeScript and timeless. What changed is how Angular consumes it: input<FlexDirection>('row') produces an InputSignal<FlexDirection>, read in templates and host bindings as flexDirection(). The host: {} object replaces both @HostBinding (for property/attribute/style/class binding) and @HostListener (for events).
Conditional dictionary helper:
type ObjectDictionary<T> = { [key: string]: T };type ArrayDictionary<T> = { [key: string]: T[] };export type Dictionary<T> = T extends [] ? ArrayDictionary<T[number]> : ObjectDictionary<T>;Built-in utility types (sample)
Exclude, Extract, Readonly, Partial, Pick, Record, ReturnType, Omit, and more — see the TypeScript Utility Types reference.
type Person = { firstName: string; lastName: string; password: string;};
type PersonWithNames = Pick<Person, 'firstName' | 'lastName'>;type PersonWithoutPassword = Omit<Person, 'password'>;Two newer utility types worth knowing for v22 Angular code:
-
Awaited<T>(TS 4.5+) — unwraps nested promises. Useful for typing the result ofawait someAsyncFunction()chains, especially with resolvers and the async resource APIs (resource(),httpResource()).typescripttype FetchUser = () => Promise<Promise<User>>;type UserResult = Awaited<ReturnType<FetchUser>>; // → User, not Promise<User> -
NoInfer<T>(TS 5.4+) — blocks TypeScript from inferring a type parameter from a particular position. Useful when you want the call site to provide a type rather than have it inferred from one argument but used to constrain another.typescriptfunction withDefault<T>(value: T | undefined, defaultValue: NoInfer<T>): T { return value ?? defaultValue;}// T is inferred from `value`; `defaultValue` is checked against it// but cannot itself drive the inference.
Angular + TypeScript today (a brief sidebar)
The Angular team has leaned harder into TypeScript over each major release. A few milestones worth knowing:
- v9 (2020): Ivy compiler. Better type inference into template expressions, stricter null checks via
strictNullChecks, type-aware AOT errors. - v12 (2021):
strictmode inng newprojects by default. Strict template type checking (strictTemplates) recommended for new code. - v14 (2022): Typed forms.
FormGroup<TControls>,FormControl<T>,FormArray<TControl>— the forms library finally has real types after years ofany. See Reactive Forms. - v16 (2023): Signal inputs —
input<T>()returnsInputSignal<T>, fully typed in both directions (component definition and binding sites). - v17 (2023): Standalone components are the default. Built-in control flow (
@if,@for,@switch) with stricter type checking than the old structural directives. - v22 (now): TypeScript 5.5+ minimum. The Application Builder (esbuild) emits TypeScript-checked output as the default. Signal Forms (experimental) leans even further into typed reactive primitives.
The trajectory: every Angular release has narrowed the gap where any could hide. Comfort with TypeScript generics, mapped types, and conditional types pays off more in v22 than it did in v9 — the framework's APIs assume it.
Summary
You now have foundational what, how, and why for TypeScript plus a taste of unions, intersections, conditional types, satisfies, and the v22-relevant utility types. Keep practicing — you'll feel more confident in Angular and the broader TS ecosystem. Explore decorators, enums, and mixins on your own; this series focuses on Angular.

