Angular (2 → 21)
In short: Angular is a TypeScript-based, component-driven framework (2016+, by Google) with a CLI, hierarchical DI, RxJS, signals, a router, forms and HTTP modules, AOT compilation and SSR.
- AngularJS (1.x) used JavaScript, controllers and
$scope, two-way binding with dirty-checking digest cycles, and reached EOL in Dec 2021. - Angular is faster, more modular, mobile-friendly, and has semver releases every six months.
In short: A component is a TypeScript class decorated with @Component that controls a piece of UI through its template and styles, with a selector used in other templates.
- A standalone component (v14+, default since v19) declares its own dependencies via
importswithout belonging to an NgModule, making apps simpler, enabling lazy loading per component, and reducing boilerplate.
In short: Interpolation {{ }} and property binding [prop]="expr" (component → view), event binding (event)="handler()" (view → component), and two-way binding [(ngModel)]="value" (banana-in-a-box: property + event).
- Also attribute
[attr.x], class[class.x]and style[style.x]bindings.
In short: Classes that add behaviour to elements.
- Components are directives with templates.
- Attribute directives change appearance/behaviour (
ngClass,ngStyle, customappHighlight). - Structural directives change DOM layout by adding/removing elements (
*ngIf,*ngFor,*ngSwitch— now largely replaced by built-in control flow@if,@for,@switch).
In short: Pipes transform values in templates (date, currency, async, custom).
- Pure pipes (default) run only when the input value/reference changes — efficient.
- Impure pipes (
pure: false) run on every change detection cycle — needed for detecting changes inside mutable objects/arrays, but expensive. asyncis impure because it tracks emissions.
The official command-line tool to scaffold, develop, build, test and update Angular apps: ng new, ng serve, ng generate component|service|pipe|guard|directive, ng build, ng test, ng e2e, ng add (add libraries with schematics), ng update (automated migrations between versions), ng lint (with angular-eslint).
In short: ngOnChanges (input changes, before init), ngOnInit (once, initialise/fetch data), ngDoCheck (each CD cycle), ngAfterContentInit/ngAfterContentChecked (projected content), ngAfterViewInit/ngAfterViewChecked (view and child views, @ViewChild ready), ngOnDestroy (cleanup).
- The constructor is for DI only.
- Modern code often uses signal inputs/
computedinstead ofngOnChanges,afterNextRenderfor DOM work andDestroyRef/takeUntilDestroyedfor cleanup.
providedIn: 'root' mean?In short: DI supplies a class's dependencies from injectors instead of the class creating them.
- Services are registered with providers; consumers use
inject(Service)or constructor parameters. @Injectable({ providedIn: 'root' })registers a tree-shakable application-wide singleton.- Providers can also be scoped to routes or components (new instance per component).
- Injectors are hierarchical: lookup goes from the element injector up to the root/environment injector.
In short: A Promise handles a single future value, is eager (starts immediately), and isn't cancellable.
- An Observable can emit many values over time, is lazy (runs on subscribe), cancellable (unsubscribe), and composable with operators (map, switchMap, debounceTime, retry).
- Angular uses Observables for HttpClient, router events, forms valueChanges; convert with
firstValueFrom/toSignalwhen convenient.
In short: All map each source value to an inner observable and flatten.
switchMapcancels the previous inner observable when a new value arrives (typeahead search, route param changes).mergeMapruns all concurrently (parallel independent requests).concatMapqueues them and runs one after another in order (sequential saves).exhaustMapignores new values while the current inner observable is active (prevent duplicate login submissions).
In short: Signals (v16+) are reactive primitives holding a value: signal() (writable), computed() (derived, memoised), effect() (side effects).
- Angular tracks which templates/computeds read which signals, so it knows exactly what changed and can update only affected components — enabling fine-grained, zoneless change detection, simpler state than RxJS for synchronous state, and better performance.
- RxJS remains useful for async event streams;
toSignal/toObservablebridge them.
In short: Template-driven forms define the model in the template with ngModel and directive validators — quick for simple forms.
- Reactive forms define
FormGroup/FormControl/FormArrayin the component, are synchronous, strongly typed (v14+), easy to unit test, and ideal for complex, dynamic forms with custom/async validators. - Most enterprise apps use reactive forms; v21 previews signal-based forms.
In short: Guards are functions (CanActivateFn, CanMatchFn, CanDeactivateFn, ResolveFn) listed on routes;
- they return boolean, UrlTree (redirect), or an observable/promise of those — e.g.
authGuardredirects to /login. - Lazy loading uses
loadComponent: () => import(...)orloadChildrenso feature code is downloaded only when the route is visited, reducing the initial bundle; - preloading strategies can fetch them in the background.
In short: Parent to child with inputs;
- child to parent with outputs (events);
- two-way with
model(); - parent to child methods via
viewChild; - siblings/unrelated components through a shared injectable service holding signals or a BehaviorSubject;
- app-wide via a store (NgRx);
- and through the router (params, query params, state).
In short: A function (HttpInterceptorFn) or class registered with provideHttpClient(withInterceptors([...])) that sees every outgoing request and incoming response.
- Uses: attaching auth tokens, adding headers, global error handling (401 redirect, toast), logging, loading spinners, caching, retrying.
- Requests are immutable — use
req.clone()to modify.
In short: Change detection syncs component state to the DOM.
- With zone.js, Angular runs a check over the tree after any async event.
OnPushlimits checks of a component to when its input references change, an event happens inside it, an async pipe emits, a signal it reads changes, ormarkForCheck()is called — fewer checks, better performance, but requires immutable updates.- Zoneless mode (default for new apps in v21) removes zone.js entirely;
- change detection is scheduled by signals, events and explicit notifications, which gives smaller bundles and more predictable updates.
In short: Unsubscribe from long-lived observables: prefer the async pipe or toSignal, use takeUntilDestroyed() (or takeUntil with a destroy subject), remove manual DOM listeners and timers in ngOnDestroy/DestroyRef.onDestroy, avoid storing component references in singleton services, and be careful with effect()s that create subscriptions.
- HttpClient requests complete on their own but still should be cancelled if the component is destroyed mid-request (switchMap/takeUntilDestroyed).
In short: Server-side rendering (@angular/ssr) renders pages on the server (or prerenders at build time) so users and search engines get HTML immediately.
- Hydration then reuses that DOM in the browser instead of re-rendering it (non-destructive hydration v16+), with event replay so early clicks aren't lost and incremental hydration (v19) to hydrate parts lazily with
@defertriggers. - Watch for browser-only APIs (
window,localStorage) — guard them withisPlatformBrowserorafterNextRender.
In short: A Redux-style state management library: actions describe events, reducers produce new immutable state, the store holds a single state tree, selectors read memoised slices, effects handle side effects like HTTP.
- Use it for large apps with complex shared state, many teams, and a need for predictability, debugging (DevTools time travel) and strict patterns.
- For small/medium apps, signal-based services or NgRx Signal Store are simpler.
In short: Use CDK virtual scrolling (cdk-virtual-scroll-viewport) so only visible rows are in the DOM;
track item.idin@for;OnPushrow components with immutable inputs;- avoid function calls and impure pipes in row templates (use computed/pure pipes);
- paginate or load incrementally from the API;
- move heavy computations to a Web Worker;
- and measure with Angular DevTools profiler.
In short: Use a reactive control and RxJS: valueChanges.pipe(debounceTime(300), distinctUntilChanged(), filter(t => t.length >= 2), switchMap(t => this.api.search(t))).
switchMapcancels the previous HTTP request, so stale responses can't overwrite newer ones.- Show results with the
asyncpipe ortoSignal, and handle errors withcatchErrorreturning an empty list.
In short: Centralise it: an auth service storing the token (in memory/signal;
- refresh token in an HttpOnly cookie via the backend), a functional interceptor that adds the
Authorizationheader to API requests, and on 401 attempts a single shared refresh (queue concurrent requests while refreshing using a shared observable), then retries; - if refresh fails, log out and redirect via a guard.
- Protect routes with
canActivate/canMatchguards; never rely on the frontend for real authorisation — the API must enforce it.
In short: In development mode Angular runs a second check after change detection;
- the error means a bound value changed between the first and second check — usually state modified in
ngAfterViewInit/ngAfterContentInit, in a child that updates the parent during the same cycle, or a getter returning a new value each time. - Fix the data flow: initialise values earlier (
ngOnInit), derive withcomputed, move updates into an event or the next tick only as a last resort, or restructure so children don't mutate parent state during rendering.
In short: Upgrade one major version at a time with ng update @angular/core@13 @angular/cli@13 (and so on), following angular.dev/update-guide for each step;
- update Angular Material and third-party libraries alongside;
- fix breaking changes and run tests after each hop;
- along the way migrate to standalone components (
ng generate @angular/core:standalone), built-in control flow (ng generate @angular/core:control-flow), signal inputs, the esbuild application builder, and functional guards/interceptors; - check Node and TypeScript version requirements.