1. ES2022+ Essentials
Angular templates and TypeScript code lean hard on a handful of modern JavaScript features. If any of the four below feel unfamiliar, that's exactly what this section fixes — you'll see all four in nearly every component you write from Week 4 onward.
Destructuring
Destructuring pulls values out of objects and arrays into named variables in one step.
It's how you'll read @Input() data, route params, and API responses
throughout the course.
Optional chaining & nullish coalescing
?. stops property access the moment it hits null or
undefined, instead of throwing. ?? supplies a fallback —
but only for null/undefined, not for every falsy value the
way || does. That distinction matters: a quantity of 0 or an
empty string '' should usually survive ??, but not ||.
interface User {
id: number;
name: string;
profile?: {
avatarUrl?: string;
};
}
function describeUser(user: User): string {
const { name, profile } = user;
const avatar = profile?.avatarUrl ?? '/assets/default-avatar.png';
return `${name} → ${avatar}`;
}
// Array destructuring + rest
const [first, second, ...remaining] = [10, 20, 30, 40];
// Spread: merge objects without mutating either one
const merged = { ...{ role: 'admin' }, ...{ active: true } };
Signal-based inputs, route data, and reactive-form values are frequently optional. ?. and ?? are how you'll handle "this might not exist yet" without littering your templates in if checks.
2. Modules, Promises & Async/Await
Every Angular file is an ES module — one file, one set of import/export
statements. And nearly every piece of data you'll show a user arrives asynchronously, via
fetch, HttpClient, or an RxJS stream. async/await
is the readable syntax sitting on top of Promises that makes that code look synchronous.
export interface User {
id: number;
name: string;
}
export async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`Failed to load user ${id}`);
}
return response.json();
}
import { fetchUser } from './user.service';
fetchUser(42)
.then(user => console.log(user.name))
.catch(err => console.error(err));
// or, inside an async function:
async function run() {
try {
const user = await fetchUser(42);
console.log(user.name);
} catch (err) {
console.error(err);
}
}
You won't write fetch calls directly once you reach Week 14
(HttpClient), but the async/await mental model transfers over exactly —
Angular's HTTP layer just wraps it in an Observable instead of a Promise.
3. Types, Interfaces & Generics
TypeScript's type system is what turns a typo like usre.name into a red
squiggle at compile time instead of a bug your users find. Interfaces describe the
shape of an object; generics let a function or type work with whatever type you give
it, without losing type safety.
interface ApiResponse<T> {
data: T;
status: number;
}
function unwrap<T>(response: ApiResponse<T>): T {
return response.data;
}
const usersResponse: ApiResponse<User[]> = { data: [], status: 200 };
const users = unwrap(usersResponse); // inferred as User[]
Notice you never told unwrap what T was — TypeScript inferred
it from the argument. This is exactly how Angular's own APIs are typed: input<T>(),
signal<T>() and HttpClient.get<T>() all use this same
generic pattern you'll now recognize on sight.
4. Union Types, Narrowing & Discriminated Unions
A union type says "this value is one of these shapes." A discriminated union
adds a shared, literal field (often called status or kind) that
lets TypeScript figure out — narrow — exactly which shape you're holding at any point in
your code.
type LoadState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User[] }
| { status: 'error'; message: string };
function renderState(state: LoadState): string {
switch (state.status) {
case 'idle':
return 'Waiting to start…';
case 'loading':
return 'Loading users…';
case 'success':
// TypeScript knows `state.data` exists here — and only here
return `Loaded ${state.data.length} users`;
case 'error':
return `Error: ${state.message}`;
}
}
The resource() API in Week 16 and every loading/error UI you build returns almost exactly this idle / loading / success / error union. Learning to model it now pays off for the rest of the course.
5. Classes & Decorators
Angular components are classes, and even in the standalone, signals-first Angular you're
learning, classes are still tagged with decorators — @Component,
@Injectable — to attach metadata the framework reads before your class is ever
instantiated. A decorator is just a function that runs on a class, method, or property
at definition time.
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: unknown[]) {
console.log(`Calling ${propertyKey} with`, args);
return original.apply(this, args);
};
}
class UserService {
@Log
getUser(id: number) {
return { id, name: 'Ada Lovelace' };
}
}
new UserService().getUser(42);
// logs: "Calling getUser with [42]"
You'll never write a decorator like @Log day to day — but seeing one built
from scratch demystifies @Component({...}): it's not magic, it's a function
that receives your class and attaches the config object as metadata Angular reads later.
6. tsconfig.json Deep Dive & Strict Mode
tsconfig.json controls how forgiving — or strict — the compiler is. The
Angular CLI generates a strict config by default, and this course keeps it that way:
strict mode catches entire categories of bugs (like forgetting a value can be
null) before your code ever runs.
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"angularCompilerOptions": {
"strictTemplates": true
}
}
strict: true is a bundle of flags, but two are worth knowing by name:
strictNullChecks—nullandundefinedare no longer silently assignable to every type, which is exactly why the?./??patterns from Section 1 exist.noImplicitAny— every parameter needs a type, either explicit or inferred. No more silentany.
strictTemplates is Angular-specific: it applies the same rigor to expressions
inside your HTML templates, so a typo in {{ user.nmae }} fails the build
instead of silently rendering nothing.
7. Hands-on Exercise
Convert a vanilla-JS utility library to strict TypeScript
Below is a small, untyped JavaScript utility file. Convert it to TypeScript under strict mode, applying everything from this week.
function truncate(str, max) {
return str.length > max ? str.slice(0, max) + '…' : str;
}
function pluck(list, key) {
return list.map(item => item[key]);
}
function safeDivide(a, b) {
if (b === 0) return { ok: false, error: 'Division by zero' };
return { ok: true, value: a / b };
}
Requirements:
- Create
string-utils.tsand add explicit types to every parameter and return value — no implicitany. - Make
pluckgeneric:pluck<T, K extends keyof T>(list: T[], key: K): T[K][], so the return type is correctly inferred from whatever list you pass in. - Model
safeDivide's return value as a discriminated unionResult<T>({ ok: true; value: T } | { ok: false; error: string }) instead of an untyped object literal. - Enable
strict: truein a localtsconfig.jsonand fix every error the compiler reports. - Write three quick call sites that prove the types work — including one that intentionally uses the result of
safeDivideand narrows onokbefore reading.value.
If pluck's generic constraint feels unfamiliar, start without it — type key as string — get everything else working, then come back and tighten it to K extends keyof T once the rest compiles.
8. Knowledge Check
Four quick questions. Expand each to check your answer — no grading, just a gut check before Week 2.
Q1
What's the actual difference between ?? and ||?
What's the actual difference between ?? and ||?
?? only falls back to its right-hand side when the left side is null or undefined. || falls back on any falsy value — including 0, '', and false. That difference matters whenever a legitimate value could be 0 or an empty string, like a quantity or a search query.
Q2
Why does Angular still use decorators like @Component if most new APIs are function-based?
Why does Angular still use decorators like @Component if most new APIs are function-based?
Decorators attach metadata to a class itself — before any instance exists. Angular needs to know "this class is a component, here's its selector and template" ahead of time, which a function called inside the class body can't express. Functions like inject() or signal() replaced patterns that happened inside a class; decorators still describe the class as a whole.
Q3
What does strict: true in tsconfig.json actually turn on?
What does strict: true in tsconfig.json actually turn on?
It's a bundle of individual flags — most notably strictNullChecks (forces you to handle null/undefined explicitly) and noImplicitAny (every value needs a real type). Also included: strictFunctionTypes, strictPropertyInitialization, strictBindCallApply, and a few more — all defaulted to their strictest setting.
Q4
Why is switching on a discriminant safer than an if/else chain that checks individual properties?
Why is switching on a discriminant safer than an if/else chain that checks individual properties?
TypeScript narrows the type inside each case automatically, so you get autocomplete and type-checking on exactly the fields that variant has. It also enables exhaustiveness checking: if you add a new variant to the union later and forget to handle it in the switch, assigning the unhandled case to a never-typed variable will fail to compile — catching the gap immediately instead of at runtime.