Angular is TypeScript-first. You do not need to be an expert, and there is a short list of features it leans on constantly — which is not quite the list a general TypeScript tutorial covers.
Interfaces for API shapes
This is the highest-value TypeScript in any application. One file describing what the server returns:
export interface ProductSize {
id: UUID;
size: SizeName;
price: number;
}The demo app keeps all of them in core/models.ts, mirroring the backend's DTOs
exactly. If the shapes drift, TypeScript fails the build instead of the UI failing at
runtime — which is the entire argument for typing an API boundary.
Union types instead of enums
export type ProductType = 'PIZZA' | 'DRINK';
export type SizeName = 'SMALL' | 'MEDIUM' | 'LARGE';
export type ToppingCategory = 'MEAT' | 'VEGGIE' | 'CHEESE';
export type OrderType = 'DELIVERY' | 'CARRYOUT';A string union gives you autocomplete and a compile error on a typo, and it is exactly
the JSON the server sends. A TypeScript enum generates a runtime object and a value that
is not the string, which then needs converting at the boundary. Prefer the union.
Type aliases that carry intent
export type UUID = string;Structurally this is just string. Its value is documentation at every use site: it
is a UUID, not "some string". The comment on that line in the app explains why the API exposes UUIDs
at all — sequential ids would let anyone walk /api/orders/1, /2,
/3 and read other people's orders.
Generics
get<T>(path: string): Observable<T> {
return this.http.get<T>(path);
}<T> is a type the caller chooses. api.get<Product[]>('/api/products')
returns Observable<Product[]> — one method, correctly typed for every endpoint.
You will mostly use generics rather than write them, and they are everywhere in
Angular: input<Filter>(), output<Product>(),
signal<CartItem[]>([]).
Decorators
@Component({
selector: 'app-spinner',A decorator attaches metadata to a class. @Component, @Directive,
@Injectable and @Pipe are the four you will meet, and they are how Angular
knows a class is a component rather than an ordinary class.
Notably, the member decorators are on the way out: @Input() and
@Output() have been replaced by the input() and output()
functions. Class decorators are staying.
strict mode
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,New projects get this by default. The part that changes how you write code is
strictNullChecks: User | null is not User, and the compiler
makes you handle the difference.
That is a feature. Most runtime errors in a frontend are something being null when you assumed it was not, and this converts them into build errors.
readonly displayName = computed(() => this._user()?.fullName ?? this._user()?.email ?? 'Account');?. stops at null; ?? supplies a fallback for null or undefined
specifically — unlike ||, which also fires for 0 and the empty string. In an
application full of prices and quantities, that distinction is a real bug rather than a nicety.
Avoid the escape hatches
as any and the non-null assertion ! both silence the compiler without
changing the runtime. They have their uses — the template's $any($event.target) is one —
but each is a place you have promised something the compiler could not verify.
readonly
private readonly api = inject(ApiService);
readonly items = this._items.asReadonly();Nearly every field in the demo app is readonly, and the habit is worth copying. An
injected service is never reassigned; neither is a signal — you call set() on it, which
is not reassignment. Marking them says so and makes an accidental reassignment a compile error.
What is next
The first real Angular concept: components.