A
Angular Vision
Angular 16 · Visual Learning Studio

Learn Angular
Visually

Stop reading about components. Start seeing data flow. Interactive animations, live code, and hands-on demos for every concept that feels hard.

🏗️ Angular Basics
📝 Templates & Directives
🟢 Lifecycle Hooks
🔴 Component Communication
💉 Services & DI
🟣 Observables & RxJS
🔵 Angular Routing
🌐 HTTP Client
📋 Reactive Forms
🔧 Pipes
🔄 Change Detection
⚡ Signals
⭐ Quick Reference
Component Communication
The 4 ways Angular components talk to each other — animated and interactive.
① @Input
② @Output
③ Service / Subject
④ ViewChild
💡 @Input() lets a Parent pass data down to a Child component. The parent controls what the child displays. Click the button to see data flow!
🎮 Interactive Demo
Parent Component
Type a message and send it to the child:
@Input() message: string
Child Component
Received via @Input():
Waiting for data...
📄 Angular 16 Code
// parent.component.html <app-child [message]="parentMessage" ></app-child> // parent.component.ts export class ParentComponent { parentMessage = 'Hello from Parent!'; } // child.component.ts import { Input } from '@angular/core'; export class ChildComponent { @Input() message: string = ''; } // child.component.html <p>{{ message }}</p>
✅ Rule: Square brackets [property] = bind to @Input. Data flows from parent template to child @Input.
💡 @Output() + EventEmitter lets a Child send events up to a Parent. The child emits, the parent listens.
🎮 Interactive Demo
Parent Component
Events received from Child:
Waiting for child events...
@Output() buttonClicked = EventEmitter
Child Component
Click the button to emit an event to parent:
Emitted 0 times
📄 Angular 16 Code
// child.component.ts import { Output, EventEmitter } from '@angular/core'; export class ChildComponent { @Output() buttonClicked = new EventEmitter<string>(); onButtonClick() { this.buttonClicked.emit('User clicked!'); } } // child.component.html <button (click)="onButtonClick()"> Click Me </button> // parent.component.html <app-child (buttonClicked)="onReceive($event)" ></app-child> // parent.component.ts onReceive(data: string) { console.log(data); }
✅ Rule: Parentheses (event) = listen to @Output. Data flows UP from child to parent.
💡 A Service + BehaviorSubject enables communication between any components — siblings, cousins, unrelated — with no direct parent-child link needed.
🎮 Interactive Demo
Component A
Updates the shared service:
⚙️
SharedService
value$:
BehaviorSubject
Component B
Subscribed to service:
Subscribing...
📄 Angular 16 Code
// shared.service.ts import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class SharedService { private value$ = new BehaviorSubject<string>(''); value$ = this.value$.asObservable(); setValue(val: string) { this.value$.next(val); } } // component-a.ts constructor(private svc: SharedService) {} update(val: string) { this.svc.setValue(val); } // component-b.ts value!: string; constructor(private svc: SharedService) {} ngOnInit() { this.svc.value$.subscribe( val => this.value = val ); }
💡 @ViewChild gives a Parent a direct reference to a Child component or DOM element — like calling a method directly on the child instance.
🎮 Interactive Demo
Parent Component
Parent directly calls child's method:
Output will appear here...
@ViewChild(ChildComponent) child!
Child Component
count = 0
Waiting for parent to call my methods...
📄 Angular 16 Code
// parent.component.ts import { ViewChild, AfterViewInit } from '@angular/core'; import { ChildComponent } from './child'; export class ParentComponent implements AfterViewInit { @ViewChild(ChildComponent) child!: ChildComponent; ngAfterViewInit() { // child is ready here console.log(this.child.count); } callChildMethod() { this.child.showAlert(); // direct call! } } // child.component.ts export class ChildComponent { count = 0; showAlert() { alert('Child method called!'); } }
⚠️ Only use @ViewChild when @Input/@Output truly won't work. It creates tight coupling and only works after ngAfterViewInit.
Observables & RxJS
See data streams in motion. Interact with operators and watch values transform in real-time.
📺
YouTube Channel
Videos are published over time.
You subscribe to receive them.
You can unsubscribe anytime.
=
🌊
Observable Stream
Values emitted over time.
You .subscribe() to receive them.
.unsubscribe() to stop.
Observable Basics
🎯 Marble Diagram
Operators Gallery
Subjects
🎮 Observable Life Cycle
Observable (the stream)
An Observable is a recipe for producing values. Nothing happens until you subscribe.
Subscribe (start receiving)
Not subscribed yet...
Unsubscribe (stop receiving)
📄 Angular 16 Code
import { Observable, interval } from 'rxjs'; import { Subscription } from 'rxjs'; // 1. CREATE an Observable (just a blueprint) const counter$ = interval(1000); // emits 0,1,2,3... // 2. SUBSCRIBE to start receiving values const sub: Subscription = counter$.subscribe({ next: (val) => console.log(val), // each value error: (err) => console.error(err), // errors complete: () => console.log('Done!') // when finished }); // 3. UNSUBSCRIBE — ALWAYS do this in ngOnDestroy! ngOnDestroy() { sub.unsubscribe(); } // ✨ Or use the async pipe in template // (auto-unsubscribes!) // {{ counter$ | async }}
🎯 Click the timeline to add marbles (values), then choose an operator to see how it transforms the stream. Each circle = one emitted value over time.
Operator:
👆 Click the source track to add marbles
Source Observable$
→
⬇️
No operator applied
Values pass through unchanged
Output Observable$
→
Click source marbles to remove them
🔄 Transformation
Transform each emitted value:
map(x => x * 2)
Switch to inner Observable (HTTP use case!):
switchMap(id => this.http.get(id))
Merge inner Observables:
mergeMap(x => getSomething(x))
Accumulate values:
scan((acc, x) => acc + x, 0)
🔍 Filtering
Only emit values that pass a test:
filter(x => x > 5)
Take only first N values:
take(3)
Wait for silence (great for search!):
debounceTime(300)
Only if different from last value:
distinctUntilChanged()
🔗 Combining
Combine latest from 2+ streams:
combineLatest([a$, b$])
Merge multiple streams into one:
merge(stream1$, stream2$)
Wait for ALL to complete:
forkJoin([a$, b$])
Handle errors gracefully:
catchError(err => of(null))
⚡ Creating Observables
From a single value:
of('hello', 'world')
From an array:
from([1, 2, 3])
Every N milliseconds:
interval(1000)
HTTP request (returns Observable!):
this.http.get('/api/data')
📡 A Subject is both an Observable AND an Observer — it can emit values AND you can subscribe to it. Perfect for services!
📦 Subject
New subscribers miss past values. Like a live concert — if you arrive late, you missed it.
const s = new Subject<number>(); s.next(1); // subscribers get 1 s.next(2); // subscribers get 2
📼 BehaviorSubject
New subscribers get the LATEST value immediately. Like Netflix — you can start watching from where it is now.
const bs = new BehaviorSubject(0); bs.subscribe(v => log(v)); // gets 0 now bs.next(42); // gets 42 // new subscriber: gets 42 immediately
📹 ReplaySubject
New subscribers get the last N values. Like a DVR — replay the last 3 episodes.
// Replays last 3 values to new subs const rs = new ReplaySubject(3);
🏁 AsyncSubject
Only emits the LAST value when it completes. Like a race — only the final winner matters.
const as = new AsyncSubject(); as.next(1); as.next(2); as.next(3); as.complete(); // subscribers only get: 3
💡 In Angular services, almost always use BehaviorSubject. It lets new components immediately get the current state without waiting for the next emission.
Lifecycle Hooks
Every Angular component lives through a predictable sequence of events. Click the buttons to see which hooks fire and when.
constructor()
Class instantiated. Inject dependencies here. DOM not ready yet.
When: Component class is first created
ngOnChanges(changes)
An @Input() property changed. Called BEFORE ngOnInit.
When: Any @Input changes (can fire many times)
ngOnInit()
⭐ Most used! Initialize data, call HTTP, subscribe to streams.
When: After first ngOnChanges. Fires ONCE.
ngDoCheck()
Custom change detection. Use sparingly — fires very often!
When: Every change detection cycle
ngAfterContentInit()
ng-content (projected content) is initialized. Fires ONCE.
When: After content projection done
ngAfterContentChecked()
Projected content change detection ran.
When: After every content check
ngAfterViewInit()
⭐ Use @ViewChild here! Component's view + child views initialized.
When: After view + children rendered. Fires ONCE.
ngAfterViewChecked()
View change detection finished. Careful — fires very often!
When: After every view check
ngOnDestroy()
⭐ Always unsubscribe here! Cleanup before component is removed.
When: Just before component is destroyed
📋 Hook Execution Log
Click "Create Component" to begin →
Angular Router
Angular's router maps URLs to components. Click routes to see how router-outlet swaps views.
🗺️ Route Tree (click to navigate)
/home → HomeComponent
/about → AboutComponent
/about/team → TeamComponent child route
/users/:id → UserProfileComponent :param
/admin → AdminDashboard canActivate guard
** (wildcard) → NotFoundComponent
📍 <router-outlet> renders here:
🏠 HomeComponent
URL: /home
📄 Angular 16 Code
// app-routing.module.ts const routes: Routes = [ { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, { path: 'about', component: AboutComponent, children: [ { path: 'team', component: TeamComponent } ] }, { path: 'users/:id', component: UserProfileComponent }, { path: 'admin', component: AdminDashboard, canActivate: [AuthGuard] // route guard }, { path: '**', component: NotFoundComponent } ]; // In template: use routerLink <a [routerLink]="['/users', userId]">Profile</a> // Read route params in component constructor(private route: ActivatedRoute) {} ngOnInit() { const id = this.route.snapshot.paramMap.get('id'); } // Navigate programmatically constructor(private router: Router) {} this.router.navigate(['/home']);
Angular 16 Cheat Sheet
The most commonly used Angular patterns — keep this tab open when coding.
🔴 Binding Syntax
Property binding (one-way, parent → child):
[property]="expression"
Event binding (child → parent):
(event)="handler($event)"
Two-way binding (needs FormsModule):
[(ngModel)]="variable"
String interpolation:
{{ expression }}
Template reference:
#myRef | @ViewChild('myRef')
⭐ Built-in Directives
Conditionally show/remove element:
*ngIf="condition" *ngIf="user; else noUser"
Iterate over a list:
*ngFor="let item of items; let i=index"
Switch-case for templates:
[ngSwitch]="val" *ngSwitchCase="'case'" *ngSwitchDefault
Conditional classes:
[ngClass]="{'active': isActive}" [class.active]="isActive"
Conditional styles:
[ngStyle]="{'color': textColor}" [style.color]="textColor"
🟣 RxJS Patterns
HTTP GET with Observable:
this.http.get<User[]>('/api/users')
Search (type → HTTP call):
this.searchInput.valueChanges.pipe( debounceTime(300), distinctUntilChanged(), switchMap(q => this.api.search(q)) )
Auto-unsubscribe in template:
{{ data$ | async }}
takeUntilDestroyed (Angular 16+):
obs$.pipe(takeUntilDestroyed())
🟢 Lifecycle Quick Ref
HTTP calls, subscribe to streams:
ngOnInit() { ... }
React to @Input changes:
ngOnChanges(changes: SimpleChanges) { }
Access @ViewChild elements:
ngAfterViewInit() { this.child... }
Always clean up here:
ngOnDestroy() { this.sub.unsubscribe(); }
🔵 Component Anatomy
Full component decorator:
@Component({ selector: 'app-name', templateUrl: './name.component.html', styleUrls: ['./name.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush })
🔥 Reactive Forms
Create form:
form = this.fb.group({ name: ['', Validators.required], email: ['', [Validators.required, Validators.email]] });
Access control value:
this.form.get('name')?.value this.form.controls['name'].value
Watch for changes:
this.form.valueChanges.subscribe(...)
Angular Basics
Understand what Angular is, how components are built, and how a project is organized — all visually.
What is Angular
Component Anatomy
Project Structure
Decorators
💡 Angular is a full framework — it gives you everything built-in: routing, forms, HTTP, DI. Compare it below.
🍦 Plain JS
Manual DOM manipulation — query elements, add listeners, track state yourself. Chaos at scale.
Manual DOMNo structure
⚛️ React
A UI library — handles view only. You pick your own router, state manager, HTTP, form library.
View onlyBring your own tools
🅰️ Angular
A complete framework. Built-in router, forms, HTTP, DI, animations — opinionated, consistent, enterprise-ready.
Full frameworkEnterprise-ready
🎯 Key Angular Features
��
Components
Self-contained UI blocks with HTML, CSS, and logic.
💉
Dependency Injection
Angular automatically provides services to components.
📝
TypeScript-first
Types catch bugs early and enable great IDE support.
🛣️
Built-in Router
Navigate views without full page reloads. Lazy loading built-in.
🌊
RxJS Observables
Reactive streams for HTTP, events, and state management.
🔁
Two-way Binding
[(ngModel)] or reactive forms sync template ↔ component.
🔍 Click any colored part of the component code to see what it does. Every Angular component is made of these parts.
🎯 Click Parts to Explore
@Component({ selector: 'app-user-card', templateUrl: './user-card.component.html', styleUrls: ['./user-card.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class UserCardComponent { @Input() user!: User; @Output() selected = new EventEmitter<User>(); ngOnInit() { // component is ready, start HTTP calls here } onSelect() { this.selected.emit(this.user); } }
👆 Click any colored part above to learn what it does.
📚 Quick Reference
selector
How you use the component: <app-user-card>
templateUrl
Separate HTML file. Use template: '…' for inline.
styleUrls
CSS is scoped — styles don't leak to other components!
@Input()
Receives data from the parent component.
@Output()
Emits events up to the parent component.
Lifecycle Hooks
Methods Angular calls at key moments: ngOnInit, ngOnDestroy…
📁 Click any file to learn what it does. This is a standard ng new my-app structure.
📂 Project File Tree
📁 my-angular-app/
⚙️ angular.json
📦 package.json
📝 tsconfig.json
📁 src/
🚀 main.ts
🌐 index.html
🎨 styles.scss
📁 app/
🏗️ app.module.ts
🛣️ app-routing.module.ts
🧩 app.component.ts
📄 app.component.html
🎨 app.component.scss
🔧 user.service.ts
📖 app.module.ts explained
@NgModule({ declarations: [ AppComponent, // your components UserCardComponent, ], imports: [ BrowserModule, // always needed RouterModule, HttpClientModule, // for HTTP calls ReactiveFormsModule ], providers: [ UserService // old-style DI ], bootstrap: [AppComponent] }) export class AppModule {}
🎨 Decorators are functions starting with @ — they add metadata to classes, telling Angular how to treat them.
@Component
Marks a class as an Angular component
@Component({ selector: 'app-root', template: `<h1>Hello!</h1>`, styles: [`h1{color:red}`] }) export class MyComponent {}
@Injectable
Marks a class as a service for DI
@Injectable({ providedIn: 'root' // singleton — one shared instance }) export class UserService { getUsers() { ... } }
@NgModule
Groups related components, pipes, directives
@NgModule({ declarations: [MyComp], imports: [CommonModule], exports: [MyComp] }) export class SharedModule {}
@Pipe
Creates a custom template transformation
@Pipe({ name: 'truncate' }) export class TruncatePipe implements PipeTransform { transform(v: string, n=50) { return v.slice(0,n)+'...'; } }
@Directive
Modifies behavior of existing DOM elements
@Directive({ selector: '[appHighlight]' }) export class HighlightDirective { @HostListener('mouseenter') onEnter() { this.el.style.background='yellow'; } }
Templates & Directives
Master Angular's template syntax — binding, structural directives, and attribute directives with live interactive demos.
Data Binding
*ngIf
*ngFor
Attr Directives
🔗 Angular's binding syntax connects component logic to the DOM. The direction of brackets tells you the direction of data flow.
Binding Cheat Sheet
{{ expression }} — Interpolation
<h1>Hello {{ name }}</h1>
Reads component property → inserts text into DOM
[property] — Property Binding
<img [src]="imageUrl">
<input [disabled]="isLoading">
Component → DOM element property (not attribute!)
(event) — Event Binding
<button (click)="save()">
<input (keyup)="onKey($event)">
DOM event → calls component method
[(ngModel)] — Two-Way Binding
<input [(ngModel)]="username">
<p>Hi {{ username }}</p>
Banana-in-a-box 🍌 — syncs both ways
Live Two-Way Binding Demo
Type below and watch the component property update in real time
Template: [(ngModel)]
⇄
Component property: name
 
🔀 *ngIf adds or removes DOM elements based on a boolean — it's not just hiding them!
🎮 Interactive Demo
Component state:
isLoggedIn = true
isAdmin = false
Rendered output:
📝 Template Code
<ng-container *ngIf="isLoggedIn; else loginBlock"> <p>Welcome back! 👋</p> <div *ngIf="isAdmin"> 🔑 Admin Panel </div> </ng-container> <ng-template #loginBlock> <p>Please log in first</p> </ng-template>
⚠️*ngIf destroys the component — it calls ngOnDestroy. Use [hidden] if you want to keep it alive.
🔄 *ngFor loops over arrays to create repeated DOM elements. Always use trackBy for performance!
🎮 Interactive Demo
📝 Template Code
<ul> <li *ngFor="let user of users; let i = index; let first = first; let last = last; trackBy: trackByFn" [class.highlighted]="user.active"> {{ i+1 }}. {{ user.name }} <span *ngIf="first"> 🥇</span> </li> </ul>
trackByFn(i: number, u: User) { return u.id; // use unique id! }
✨ Attribute directives modify the appearance or behavior of an element — they don't add/remove from the DOM like structural directives.
[ngClass]
Add/remove CSS classes dynamically
I change classes!
<div [ngClass]="{ active: isActive, error: hasError, large: isLarge }">
[ngStyle]
Apply inline styles dynamically
Dynamic font size!
<div [ngStyle]="{ fontSize: size + 'px', color: textColor, opacity: visible ? 1 : 0 }">
Services & Dependency Injection
Learn how Angular's DI system shares logic and data across components — making your code cleaner and testable.
What is DI
Injector Tree
Creating Services
Service Scopes
💡 Without DI, every component creates its own service instance — losing shared state. With DI, Angular creates ONE instance and gives it to everyone who asks.
❌ Without DI (bad)
class ProductListComponent { // Creates its OWN service! service = new ProductService(); } class CartComponent { // Different instance — no shared data! service = new ProductService(); }
⚠️Two separate instances = data won't sync!
✅ With DI (correct)
class ProductListComponent { // Angular provides the SAME instance constructor(private svc: ProductService) {} } class CartComponent { // Same instance — shared data! constructor(private svc: ProductService) {} }
✅One singleton — both components share state!
🌳 Angular has a tree of injectors. When a component needs a service, Angular walks up the tree until it finds a provider.
🔍 Click a node to trace service lookup
Root Injector
UserService, HttpClient
Lazy Module Injector
OrderService
Component Injector
(no providers)
📝 Provider Locations
// 1. Root (global singleton) @Injectable({ providedIn: 'root' }) export class UserService {} // 2. Module (scoped to module) @NgModule({ providers: [OrderService] }) export class OrdersModule {} // 3. Component (scoped to component) @Component({ providers: [LocalService] }) export class MyComponent {}
🔧 A service is just a TypeScript class with @Injectable(). It can hold data, make HTTP calls, and contain business logic.
🎮 Live Shopping Cart Service
ProductListComponent uses:
CartService.items$ → 0 items
CartComponent uses:
CartService.total$ → $0.00
📝 CartService Code
@Injectable({ providedIn: 'root' }) export class CartService { private _items = new BehaviorSubject<Item[]>([]); items$ = this._items.asObservable(); total$ = this.items$.pipe( map(items => items.reduce((sum, i) => sum + i.price, 0) ) ); addItem(item: Item) { const cur = this._items.getValue(); this._items.next([...cur, item]); } }
🎯 Where you provide a service determines how many instances exist and who can access it. Choose wisely!
🌍 Root Scope
@Injectable({ providedIn: 'root' })
One instance for the entire app. Tree-shaken if unused. Best for shared singletons like AuthService, CartService.
SingletonTree-shaken
📦 Module Scope
@NgModule({ providers: [MyService] })
One instance per module. Use for lazy-loaded features that shouldn't share with root.
Module-scoped
🧩 Component Scope
@Component({ providers: [LocalService] })
New instance per component. Destroyed when component is destroyed. Use for component-specific state.
Per-component
Reactive Forms
Build powerful, type-safe forms with full control over validation, state, and dynamic form arrays — all in TypeScript.
Core Concepts
Form Builder
Validation
Form Arrays
📋 Reactive Forms define the form structure in TypeScript — not the template. This makes them predictable, testable, and powerful.
FormControl
A single input field — tracks value, validity, touched/dirty states.
const email = new FormControl( '', [Validators.required, Validators.email] );
FormGroup
A collection of related FormControls — like a form section.
const form = new FormGroup({ name: new FormControl(''), email: new FormControl(''), age: new FormControl(0) });
FormArray
A dynamic array of FormControls — add/remove items at runtime.
const tags = new FormArray([ new FormControl('angular'), new FormControl('rxjs') ]);
🏗️ FormBuilder is a shortcut service — it reduces boilerplate dramatically. Use it in production!
📋 Live Registration Form
Status: INVALID — fill the form
📝 FormBuilder Code
export class RegComponent { form = this.fb.group({ name: ['', [ Validators.required, Validators.minLength(2) ]], email: ['', [ Validators.required, Validators.email ]], password: ['', [ Validators.required, Validators.minLength(8) ]] }); constructor(private fb: FormBuilder) {} onSubmit() { if (this.form.valid) { console.log(this.form.value); } } }
✅ FormControl tracks valid/invalid, touched/untouched, and dirty/pristine states. Show errors only when the user has interacted!
Form Control States
valid / invalid
Does the value pass all validators?
ctrl.valid → true/false
touched / untouched
Has the user focused+blurred the field?
ctrl.touched → true/false
dirty / pristine
Has the user changed the value?
ctrl.dirty → true/false
errors
Object with failed validator keys
ctrl.errors?.required
💡 Best practice: *ngIf="ctrl.invalid && ctrl.touched" — only show errors after user has interacted!
🔄 FormArray lets you add/remove form fields dynamically — perfect for skills lists, phone numbers, addresses.
🎮 Dynamic Skills List
form.value: { skills: [] }
📝 FormArray Code
form = this.fb.group({ skills: this.fb.array([ this.fb.control('Angular') ]) }); get skills() { return this.form.get('skills') as FormArray; } addSkill() { this.skills.push( this.fb.control('') ); } removeSkill(i: number) { this.skills.removeAt(i); }
HTTP Client
Make HTTP requests with Angular's built-in HttpClient — returns Observables you can pipe through RxJS operators.
Request Pipeline
HTTP Methods
Interceptors
Error Handling
🌊 Every HTTP call returns an Observable. Click Send Request to watch data flow through each stage.
HTTP Pipeline Visualizer
🧩
Component
calls http.get()
→
🔑
Auth Interceptor
adds Bearer token
→
📝
Log Interceptor
logs request
→
🌐
HTTP Handler
actual network call
→
📦
Response
JSON → typed object
→
✅
Component
updates template
Click "Send Request" to start
📝 Component Code
export class UsersComponent { users$: Observable<User[]>; constructor(private http: HttpClient) { this.users$ = this.http .get<User[]>('/api/users') .pipe( tap(u => console.log(u)), catchError(err => { return of([]); }) ); } }
📝 Template (async pipe)
<ng-container *ngIf="users$ | async as users"> <ul> <li *ngFor="let u of users"> {{ u.name }} </li> </ul> </ng-container> <!-- async pipe auto-unsubscribes! -->
📡 HttpClient methods match HTTP verbs — GET, POST, PUT, PATCH, DELETE. All return Observables.
GET — Fetch Data
// List users http.get<User[]>('/api/users') // With query params http.get<User[]>('/api/users', { params: { page: 1, limit: 10 } })
POST — Create
// Create a new user http.post<User>('/api/users', { name: 'Alice', email: 'alice@example.com' })
PUT / PATCH — Update
// Full replace (PUT) http.put<User>(`/api/users/${id}`, user) // Partial update (PATCH) http.patch<User>(`/api/users/${id}`, { name: 'New Name' } )
DELETE — Remove
// Delete a user http.delete(`/api/users/${id}`) // With response body type http.delete<{ success: boolean }>( `/api/users/${id}` )
🔑 Interceptors sit between your code and the network — perfect for adding auth headers, logging, or caching on every request.
📝 Auth Interceptor
@Injectable() export class AuthInterceptor implements HttpInterceptor { intercept( req: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> { const token = localStorage .getItem('token'); const cloned = req.clone({ headers: req.headers.set( 'Authorization', `Bearer ${token}` ) }); return next.handle(cloned); } }
📝 Register Interceptor
@NgModule({ providers: [{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: LogInterceptor, multi: true }] })
💡multi: true is required — it tells Angular to keep all interceptors, not replace previous ones.
⚠️ Always handle HTTP errors! An unhandled error will terminate your Observable stream.
Error Handling Patterns
catchError + of([])
this.users$ = this.http .get<User[]>('/api/users') .pipe( catchError(err => { console.error(err); return of([]); // fallback }) );
retry on failure
this.data$ = this.http .get('/api/data') .pipe( retry(3), // retry 3 times catchError(err => throwError(() => err) ) );
Pipes
Transform data right in your templates — format dates, numbers, currencies, and create your own custom pipes.
Pipe Playground
Built-in Pipes
Custom Pipes
Async Pipe
🎮 Type a value and select a pipe to see it transform in real time — just like Angular does in your templates!
Live Pipe Transformer
Template expression
{{ "hello world" | uppercase }}
→
Output
HELLO WORLD
🔧 Angular ships with many useful pipes. Import CommonModule to use them all.
date
{{ now | date }} → Jun 23, 2024 {{ now | date:'dd/MM/yyyy' }} → 23/06/2024 {{ now | date:'HH:mm' }} → 14:35
currency
{{ 1234.56 | currency }} → $1,234.56 {{ 1234 | currency:'EUR' }} → €1,234.00 {{ 1234 | currency:'GBP':'symbol-narrow' }} → £1,234.00
number / percent
{{ 3.14159 | number:'1.2-2' }} → 3.14 {{ 0.75 | percent }} → 75% {{ 0.75 | percent:'2.1-1' }} → 75.0%
slice / json / keyvalue
{{ 'Angular' | slice:0:3 }} → Ang {{ obj | json }} → {"key": "value"} *ngFor="let p of obj | keyvalue" → {key: 'name', value: 'Alice'}
String Pipes
{{ 'hello' | uppercase }} → HELLO {{ 'HELLO' | lowercase }} → hello {{ 'hello world' | titlecase }} → Hello World
⚒️ Create custom pipes when built-ins don't cover your needs. The transform() method is called every time the input changes.
📝 Truncate Pipe
@Pipe({ name: 'truncate' }) export class TruncatePipe implements PipeTransform { transform( value: string, limit = 50, trail = '...' ): string { if (!value) return ''; return value.length > limit ? value.slice(0, limit) + trail : value; } }
<!-- In template --> {{ description | truncate }} {{ description | truncate:20 }} {{ description | truncate:20:'…' }}
🎮 Live Truncate Demo
{{ text | truncate : limit }}
⭐ The async pipe is Angular's superpower — it subscribes to Observables/Promises AND automatically unsubscribes when the component is destroyed!
📝 Without async pipe (manual)
export class UsersComp implements OnInit, OnDestroy { users: User[] = []; private sub!: Subscription; ngOnInit() { this.sub = this.svc.users$ .subscribe(u => this.users = u); } ngOnDestroy() { this.sub.unsubscribe(); // 😰 manual! } }
📝 With async pipe (better!)
export class UsersComp { // No ngOnInit, no ngOnDestroy! users$ = this.svc.users$; constructor( private svc: UserService ) {} } <!-- Template --> <ul> <li *ngFor="let u of users$ | async"> {{ u.name }} </li> </ul>
✅Angular auto-unsubscribes! No memory leaks, no boilerplate.
Change Detection
Understand how Angular knows when to re-render, and how to optimize performance with OnPush and Signals.
Default Strategy
OnPush Strategy
Immutability
markForCheck
⚠️ Default strategy: Angular checks every component in the tree whenever any event fires (click, HTTP, timer). Great for correctness, bad for performance.
Default CD — Animate a Check Cycle
Click any component to trigger an event. Angular checks the entire tree!
AppComponent
HeaderComponent
MainComponent
ListComponent
DetailComponent
Ready — click "Trigger Change Detection"
⚡ OnPush strategy: Component only re-checks when its @Input() reference changes, it emits an event, or you call markForCheck(). Much faster!
OnPush CD — Only Marked Components Re-Check
With OnPush, Angular skips components whose inputs haven't changed (shown in grey)
AppComponent
HeaderComponent
OnPush
MainComponent
ListComponent
OnPush
DetailComponent
OnPush
Ready — click "Trigger with OnPush"
🔒 OnPush checks reference equality — mutating an object won't trigger it! Always create new objects/arrays.
❌ Mutation (OnPush misses it)
// BAD: mutating array addUser(user: User) { this.users.push(user); // Reference unchanged! // OnPush won't detect this } // BAD: mutating object updateName(name: string) { this.user.name = name; // Same object reference! }
✅ Immutable (OnPush detects it)
// GOOD: new array reference addUser(user: User) { this.users = [...this.users, user]; // New reference! OnPush re-checks } // GOOD: new object reference updateName(name: string) { this.user = { ...this.user, name }; // New reference! OnPush re-checks }
🏷️ Sometimes you need to manually trigger change detection — e.g., after async operations outside Angular's zone. Use markForCheck().
📝 markForCheck Pattern
export class DataComponent { data: string = 'loading...'; constructor( private cd: ChangeDetectorRef ) {} loadFromThirdParty() { // Non-Angular async (WebSocket, etc) thirdPartyLib.onData((value) => { this.data = value; // Angular doesn't know about this! this.cd.markForCheck(); // Now Angular will check this component }); } detachAndManually() { this.cd.detach(); // opt out entirely // control re-renders manually this.cd.detectChanges(); } }
📋 ChangeDetectorRef API
markForCheck()
Marks component and ancestors for checking on next cycle. Use this with OnPush + async code.
detectChanges()
Run change detection on this component and its children immediately, synchronously.
detach() / reattach()
Completely opt out of automatic checking. Re-attach to resume normal behavior.
Angular Signals
Signals are Angular's new reactive primitive — simpler than RxJS for local state, automatically granular change detection.
What are Signals
computed()
effect()
Signals vs RxJS
⚡ A Signal is a reactive value wrapper. When you read it inside a template or computed(), Angular tracks that dependency and re-runs when the signal changes.
🎮 Live Signal Demo
Signal: count = signal(0)
0
computed: doubled = computed(() => count() * 2)
0
effect log: effect(() => console.log(count()))
📝 Signal Code
import { signal, computed, effect } from '@angular/core'; export class CounterComponent { // 1. Create a signal count = signal(0); // 2. Derived computed signal doubled = computed(() => this.count() * 2 ); constructor() { // 3. Side effect when signal changes effect(() => { console.log('count:', this.count()); }); } increment() { this.count.update(n => n+1); } decrement() { this.count.update(n => n-1); } reset() { this.count.set(0); } }
🧮 computed() derives a new signal from other signals. It's lazy (only recalculates when read after a dependency changes) and memoized (cached until deps change).
🛒 Shopping Cart with computed()
Items:0
Subtotal:$0.00
Total (incl. 10% tax):$0.00
📝 Computed Signals
items = signal<CartItem[]>([]); // Derived — recalculates when items changes itemCount = computed(() => this.items().length ); subtotal = computed(() => this.items() .reduce((s, i) => s + i.price, 0) ); // Can compute from other computeds! total = computed(() => this.subtotal() * 1.1 ); addItem(item: CartItem) { this.items.update( prev => [...prev, item] ); }
🔔 effect() runs a side effect whenever signals it reads change. Use for logging, saving to localStorage, syncing to external systems.
📝 effect() Examples
export class UserSettings { theme = signal('dark'); fontSize = signal(16); username = signal('Alice'); constructor() { // Runs when theme OR fontSize changes effect(() => { document.body.className = `theme-${this.theme()}`; document.body.style.fontSize = this.fontSize() + 'px'; }); // Runs when username changes effect(() => { localStorage.setItem( 'user', this.username() ); }); } }
�� effect() Rules
✅ DO: Read signals inside effect
Any signal read inside effect() becomes a tracked dependency — effect re-runs when any of them change.
❌ DON'T: Write signals inside effect
Writing to a signal inside its own effect creates infinite loops. Use untracked() if necessary.
💡 Cleanup: return a function
effect(() => { const id = setInterval(...); return () => clearInterval(id); });
⚖️ Signals and RxJS solve different problems — they're complementary, not replacements. Angular provides interop functions to bridge them.
⚡ Use Signals for…
✅ Local component state
✅ Derived values (computed)
✅ Synchronous reactive values
✅ Replacing simple BehaviorSubjects
✅ Performance-sensitive UI state
🌊 Use RxJS for…
✅ HTTP requests (inherently async)
✅ Complex event streams
✅ debounceTime, switchMap, etc.
✅ Multi-source combining (combineLatest)
✅ Retry logic, error handling streams
Interop: Signals ↔ RxJS
Observable → Signal
import { toSignal } from '@angular/core/rxjs-interop'; users$ = this.http.get('/api/users'); // Convert to signal users = toSignal(this.users$, { initialValue: [] });
Signal → Observable
import { toObservable } from '@angular/core/rxjs-interop'; count = signal(0); // Convert to Observable count$ = toObservable(this.count) .pipe(debounceTime(300));