Om Pandey
Angular Framework

Angular Framework

The Modern Platform for Building Web Applications

Master Angular - Google's powerful TypeScript-based framework. Build dynamic, scalable single-page applications with a component-based architecture. From beginner to expert!

Component-Based TypeScript Two-Way Binding Modular

What You'll Learn

1

Introduction to Angular

Angular is a powerful, open-source web application framework developed by Google. It's built with TypeScript and follows a component-based architecture, making it perfect for building large-scale enterprise applications.

Why Angular?

Enterprise Ready: Angular provides a complete solution out of the box - routing, forms, HTTP client, testing, and more. Used by Google, Microsoft, Forbes, and BMW!

Angular Architecture
Component
Template
Service
Data

Components

Reusable UI building blocks

Dependency Injection

Built-in DI system

Two-Way Binding

Sync data & UI automatically

TypeScript

Type-safe development

Key Concepts:
  • Modules: Organize application into cohesive blocks
  • Components: UI building blocks with template, logic, and styles
  • Services: Shared logic and data across components
  • Directives: Extend HTML with custom attributes and elements
2

Installation & Setup

1

Install Node.js

Make sure you have Node.js 18+ installed. Check with node --version

2

Install Angular CLI

Angular CLI is the official command-line tool for Angular development

Terminal
$ # Install Angular CLI globally $ npm install -g @angular/cli added 234 packages in 25s $ # Verify installation $ ng version _ _ ____ _ ___ / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| |___/ Angular CLI: 17.0.0 Node: 20.10.0 Package Manager: npm 10.2.3
3

Create New Project

Use Angular CLI to scaffold a new project

Terminal
$ # Create new Angular project $ ng new my-angular-app ? Which stylesheet format would you like to use? CSS ? Do you want to enable Server-Side Rendering (SSR)? No CREATE my-angular-app/angular.json CREATE my-angular-app/package.json CREATE my-angular-app/tsconfig.json ... $ # Navigate to project $ cd my-angular-app $ # Start development server $ ng serve ✔ Browser application bundle generation complete. Initial Chunk Files | Names | Size main.js | main | 178 kB polyfills.js | polyfills | 33 kB styles.css | styles | 0 bytes ** Angular Live Development Server is listening on localhost:4200 **

Angular Project Created!

Open http://localhost:4200 in your browser to see your app!

3

Project Structure

Understanding the project structure is crucial for effective Angular development.

📁 my-angular-app/ ├── 📁 src/ │ ├── 📁 app/ │ │ ├── app.component.ts # Root component logic │ │ ├── app.component.html # Root component template │ │ ├── app.component.css # Root component styles │ │ ├── app.component.spec.ts # Unit tests │ │ ├── app.module.ts # Root module │ │ └── app-routing.module.ts # Routing configuration │ ├── 📁 assets/ # Static assets (images, fonts) │ ├── index.html # Main HTML file │ ├── main.ts # Application entry point │ └── styles.css # Global styles ├── angular.json # Angular CLI configuration ├── package.json # NPM dependencies ├── tsconfig.json # TypeScript configuration └── tsconfig.app.json # App-specific TS config

Folder Organization

As your app grows, organize by feature: src/app/features/, src/app/shared/, src/app/core/

4

Components

Components are the fundamental building blocks of Angular applications. Each component consists of a TypeScript class, an HTML template, and optional CSS styles.

Creating a Component

Terminal
$ # Generate a new component $ ng generate component header CREATE src/app/header/header.component.css CREATE src/app/header/header.component.html CREATE src/app/header/header.component.spec.ts CREATE src/app/header/header.component.ts UPDATE src/app/app.module.ts $ # Shorthand $ ng g c footer

Component Structure

header.component.ts
TypeScript
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-header',          // HTML tag name
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
  
  // Properties
  title: string = 'My Angular App';
  isLoggedIn: boolean = false;
  user: { name: string; email: string } | null = null;

  // Constructor
  constructor() { }

  // Lifecycle hook - runs after component initializes
  ngOnInit(): void {
    console.log('Header component initialized');
  }

  // Methods
  login(): void {
    this.isLoggedIn = true;
    this.user = { name: 'Om Pandey', email: '[email protected]' };
  }

  logout(): void {
    this.isLoggedIn = false;
    this.user = null;
  }
}
header.component.html
HTML
<header class="app-header">
  <h1>{{ title }}</h1>
  
  <nav>
    <a routerLink="/">Home</a>
    <a routerLink="/about">About</a>
    <a routerLink="/contact">Contact</a>
  </nav>

  <div class="auth-section">
    <!-- Conditional rendering -->
    <div *ngIf="isLoggedIn; else loginButton">
      <span>Welcome, {{ user?.name }}</span>
      <button (click)="logout()">Logout</button>
    </div>
    
    <ng-template #loginButton>
      <button (click)="login()">Login</button>
    </ng-template>
  </div>
</header>

Component Communication

Components communicate using @Input() and @Output() decorators.

child.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-user-card',
  template: `
    <div class="user-card">
      <h3>{{ user.name }}</h3>
      <p>{{ user.email }}</p>
      <button (click)="onSelect()">Select User</button>
    </div>
  `
})
export class UserCardComponent {
  
  // Input - receive data from parent
  @Input() user!: { name: string; email: string };
  
  // Output - send events to parent
  @Output() userSelected = new EventEmitter<string>();
  
  onSelect(): void {
    this.userSelected.emit(this.user.name);
  }
}
parent.component.html
<!-- Parent component using child -->
<app-user-card
  [user]="currentUser"
  (userSelected)="handleUserSelection($event)"
></app-user-card>

<!-- Loop through users -->
<app-user-card
  *ngFor="let user of users"
  [user]="user"
  (userSelected)="handleUserSelection($event)"
></app-user-card>

Lifecycle Hooks

lifecycle.component.ts
import { 
  Component, OnInit, OnDestroy, OnChanges, 
  AfterViewInit, SimpleChanges 
} from '@angular/core';

@Component({...})
export class LifecycleComponent implements 
    OnInit, OnDestroy, OnChanges, AfterViewInit {
  
  // Called when input properties change
  ngOnChanges(changes: SimpleChanges): void {
    console.log('Input changed:', changes);
  }
  
  // Called once after component is initialized
  ngOnInit(): void {
    console.log('Component initialized');
    // Perfect for API calls, subscriptions
  }
  
  // Called after view is fully initialized
  ngAfterViewInit(): void {
    console.log('View initialized');
    // Access @ViewChild elements here
  }
  
  // Called when component is destroyed
  ngOnDestroy(): void {
    console.log('Component destroyed');
    // Cleanup subscriptions, timers, etc.
  }
}
5

Templates & Data Binding

Angular templates use special syntax to bind data between the component and the view.

Types of Data Binding

data-binding.component.html
<!-- 1. INTERPOLATION - Display data {{ }} -->
<h1>{{ title }}</h1>
<p>{{ user.name }} - {{ user.email }}</p>
<p>{{ 2 + 2 }}</p>
<p>{{ getFullName() }}</p>

<!-- 2. PROPERTY BINDING - [ ] -->
<img [src]="imageUrl" [alt]="imageAlt">
<button [disabled]="isLoading">Submit</button>
<div [class.active]="isActive">...</div>
<div [style.color]="textColor">...</div>
<div [ngClass]="{'active': isActive, 'disabled': isDisabled}">...</div>
<div [ngStyle]="{'color': textColor, 'font-size': fontSize + 'px'}">...</div>

<!-- 3. EVENT BINDING - ( ) -->
<button (click)="handleClick()">Click Me</button>
<button (click)="handleClick($event)">With Event</button>
<input (input)="onInput($event)">
<input (keyup.enter)="onEnter()">
<form (submit)="onSubmit($event)">...</form>
<div (mouseenter)="onHover()" (mouseleave)="onLeave()">...</div>

<!-- 4. TWO-WAY BINDING - [( )] (Banana in a Box) -->
<input [(ngModel)]="username">
<p>Hello, {{ username }}!</p>

<!-- Equivalent to: -->
<input [ngModel]="username" (ngModelChange)="username = $event">

FormsModule Required!

To use [(ngModel)], import FormsModule in your module.

Template Reference Variables

template-ref.component.html
<!-- Create reference with # -->
<input #nameInput type="text" placeholder="Enter name">
<button (click)="greet(nameInput.value)">Greet</button>

<!-- Display input length -->
<p>Length: {{ nameInput.value.length }}</p>

<!-- Access in component with @ViewChild -->
component.ts - @ViewChild
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({...})
export class MyComponent implements AfterViewInit {
  
  @ViewChild('nameInput') inputRef!: ElementRef<HTMLInputElement>;
  
  ngAfterViewInit(): void {
    // Focus input on load
    this.inputRef.nativeElement.focus();
  }
  
  greet(name: string): void {
    alert(`Hello, ${name}!`);
  }
}
6

Directives

Directives are instructions in the DOM. Angular has three types: Components, Structural directives, and Attribute directives.

Structural Directives

structural-directives.html
<!-- *ngIf - Conditional rendering -->
<div *ngIf="isLoggedIn">Welcome back!</div>

<!-- *ngIf with else -->
<div *ngIf="isLoggedIn; else loginTemplate">
  Welcome, {{ userName }}!
</div>
<ng-template #loginTemplate>
  <button>Please Login</button>
</ng-template>

<!-- *ngIf with then and else -->
<div *ngIf="isLoading; then loadingTpl; else contentTpl"></div>
<ng-template #loadingTpl>Loading...</ng-template>
<ng-template #contentTpl>Content loaded!</ng-template>

<!-- *ngFor - Loop through arrays -->
<ul>
  <li *ngFor="let item of items">{{ item }}</li>
</ul>

<!-- *ngFor with index and other variables -->
<div *ngFor="let user of users; let i = index; let first = first; let last = last; let even = even">
  <p [class.highlight]="even">
    {{ i + 1 }}. {{ user.name }}
    <span *ngIf="first">(First)</span>
    <span *ngIf="last">(Last)</span>
  </p>
</div>

<!-- *ngFor with trackBy for performance -->
<div *ngFor="let item of items; trackBy: trackById">
  {{ item.name }}
</div>

<!-- ngSwitch -->
<div [ngSwitch]="status">
  <p *ngSwitchCase="'active'">User is active</p>
  <p *ngSwitchCase="'inactive'">User is inactive</p>
  <p *ngSwitchCase="'pending'">User is pending</p>
  <p *ngSwitchDefault>Unknown status</p>
</div>

Custom Directive

Terminal
$ ng generate directive highlight
highlight.directive.ts
import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  
  @Input() appHighlight: string = 'yellow';
  @Input() defaultColor: string = 'transparent';
  
  constructor(private el: ElementRef) {}
  
  @HostListener('mouseenter')
  onMouseEnter(): void {
    this.highlight(this.appHighlight || 'yellow');
  }
  
  @HostListener('mouseleave')
  onMouseLeave(): void {
    this.highlight(this.defaultColor);
  }
  
  private highlight(color: string): void {
    this.el.nativeElement.style.backgroundColor = color;
  }
}
Usage in template
<!-- Default highlight (yellow) -->
<p appHighlight>Hover over me!</p>

<!-- Custom highlight color -->
<p [appHighlight]="'lightblue'">Blue highlight</p>

<!-- With default color -->
<p appHighlight="pink" defaultColor="lightgray">Pink highlight</p>
7

Services & Dependency Injection

Services are classes that handle business logic, data fetching, and shared functionality. Angular's Dependency Injection system makes services available throughout your app.

Creating a Service

Terminal
$ ng generate service services/user CREATE src/app/services/user.service.spec.ts CREATE src/app/services/user.service.ts
services/user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';

export interface User {
  id: number;
  name: string;
  email: string;
}

@Injectable({
  providedIn: 'root'  // Available app-wide
})
export class UserService {
  
  private apiUrl = 'https://api.example.com/users';
  private currentUserSubject = new BehaviorSubject<User | null>(null);
  
  // Observable for components to subscribe
  currentUser$ = this.currentUserSubject.asObservable();
  
  constructor(private http: HttpClient) {}
  
  // Get all users
  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl);
  }
  
  // Get user by ID
  getUserById(id: number): Observable<User> {
    return this.http.get<User>(`${this.apiUrl}/${id}`);
  }
  
  // Create user
  createUser(user: Omit<User, 'id'>): Observable<User> {
    return this.http.post<User>(this.apiUrl, user);
  }
  
  // Update user
  updateUser(id: number, user: Partial<User>): Observable<User> {
    return this.http.put<User>(`${this.apiUrl}/${id}`, user);
  }
  
  // Delete user
  deleteUser(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`);
  }
  
  // Set current user
  setCurrentUser(user: User): void {
    this.currentUserSubject.next(user);
  }
}

Using Service in Component

user-list.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { UserService, User } from '../services/user.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html'
})
export class UserListComponent implements OnInit, OnDestroy {
  
  users: User[] = [];
  loading = false;
  error: string | null = null;
  private subscription!: Subscription;
  
  // Inject service via constructor
  constructor(private userService: UserService) {}
  
  ngOnInit(): void {
    this.loadUsers();
  }
  
  loadUsers(): void {
    this.loading = true;
    this.subscription = this.userService.getUsers().subscribe({
      next: (users) => {
        this.users = users;
        this.loading = false;
      },
      error: (err) => {
        this.error = 'Failed to load users';
        this.loading = false;
        console.error(err);
      }
    });
  }
  
  selectUser(user: User): void {
    this.userService.setCurrentUser(user);
  }
  
  deleteUser(id: number): void {
    this.userService.deleteUser(id).subscribe({
      next: () => {
        this.users = this.users.filter(u => u.id !== id);
      },
      error: (err) => console.error(err)
    });
  }
  
  ngOnDestroy(): void {
    // Clean up subscription
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}
8

Routing & Navigation

Angular Router enables navigation between views and supports features like lazy loading, route guards, and nested routes.

Route Configuration

app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { UserListComponent } from './user-list/user-list.component';
import { UserDetailComponent } from './user-detail/user-detail.component';
import { NotFoundComponent } from './not-found/not-found.component';
import { AuthGuard } from './guards/auth.guard';

const routes: Routes = [
  // Basic routes
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  
  // Route with parameter
  { path: 'users', component: UserListComponent },
  { path: 'users/:id', component: UserDetailComponent },
  
  // Protected route with guard
  { 
    path: 'dashboard', 
    component: DashboardComponent,
    canActivate: [AuthGuard]
  },
  
  // Lazy loaded module
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module')
      .then(m => m.AdminModule),
    canActivate: [AuthGuard]
  },
  
  // Redirect
  { path: 'home', redirectTo: '', pathMatch: 'full' },
  
  // Wildcard - 404 page
  { path: '**', component: NotFoundComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Navigation

navigation.component.html
<!-- Router outlet - where routed components render -->
<router-outlet></router-outlet>

<!-- Navigation links -->
<nav>
  <a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
    Home
  </a>
  <a routerLink="/about" routerLinkActive="active">About</a>
  <a routerLink="/users" routerLinkActive="active">Users</a>
  
  <!-- Dynamic route -->
  <a [routerLink]="['/users', userId]">User Profile</a>
  
  <!-- With query params -->
  <a [routerLink]="['/users']" [queryParams]="{page: 1, sort: 'name'}">
    Users Page 1
  </a>
</nav>
Programmatic Navigation
import { Router, ActivatedRoute } from '@angular/router';

export class UserDetailComponent implements OnInit {
  userId!: string;
  
  constructor(
    private router: Router,
    private route: ActivatedRoute
  ) {}
  
  ngOnInit(): void {
    // Get route parameter
    this.userId = this.route.snapshot.paramMap.get('id') || '';
    
    // Subscribe to param changes
    this.route.paramMap.subscribe(params => {
      this.userId = params.get('id') || '';
    });
    
    // Get query params
    this.route.queryParamMap.subscribe(params => {
      const page = params.get('page');
    });
  }
  
  goToUsers(): void {
    this.router.navigate(['/users']);
  }
  
  goToUserWithParams(): void {
    this.router.navigate(['/users', 123], {
      queryParams: { tab: 'profile' }
    });
  }
}

Route Guards

guards/auth.guard.ts
import { Injectable } from '@angular/core';
import { 
  CanActivate, Router, ActivatedRouteSnapshot, 
  RouterStateSnapshot, UrlTree 
} from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  
  constructor(
    private authService: AuthService,
    private router: Router
  ) {}
  
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean | UrlTree> | boolean | UrlTree {
    
    if (this.authService.isLoggedIn()) {
      return true;
    }
    
    // Redirect to login with return URL
    return this.router.createUrlTree(['/login'], {
      queryParams: { returnUrl: state.url }
    });
  }
}
9

Forms

Angular provides two approaches to forms: Template-driven and Reactive forms.

Template-Driven Forms

template-form.component.html
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
  
  <div class="form-group">
    <label for="name">Name</label>
    <input
      type="text"
      id="name"
      name="name"
      [(ngModel)]="user.name"
      required
      minlength="3"
      #name="ngModel"
    >
    <div *ngIf="name.invalid && name.touched" class="error">
      <span *ngIf="name.errors?.['required']">Name is required</span>
      <span *ngIf="name.errors?.['minlength']">Min 3 characters</span>
    </div>
  </div>
  
  <div class="form-group">
    <label for="email">Email</label>
    <input
      type="email"
      id="email"
      name="email"
      [(ngModel)]="user.email"
      required
      email
      #email="ngModel"
    >
    <div *ngIf="email.invalid && email.touched" class="error">
      Please enter a valid email
    </div>
  </div>
  
  <button type="submit" [disabled]="userForm.invalid">Submit</button>
  
</form>

Reactive Forms

reactive-form.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-reactive-form',
  templateUrl: './reactive-form.component.html'
})
export class ReactiveFormComponent implements OnInit {
  
  userForm!: FormGroup;
  
  constructor(private fb: FormBuilder) {}
  
  ngOnInit(): void {
    this.userForm = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(3)]],
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(8)]],
      address: this.fb.group({
        street: [''],
        city: ['', Validators.required],
        zipCode: ['', Validators.pattern(/^\d{5}$/)]
      })
    });
  }
  
  // Convenience getters
  get name() { return this.userForm.get('name'); }
  get email() { return this.userForm.get('email'); }
  get password() { return this.userForm.get('password'); }
  
  onSubmit(): void {
    if (this.userForm.valid) {
      console.log(this.userForm.value);
    }
  }
  
  resetForm(): void {
    this.userForm.reset();
  }
}
reactive-form.component.html
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
  
  <div class="form-group">
    <label>Name</label>
    <input formControlName="name" type="text">
    <div *ngIf="name?.invalid && name?.touched" class="error">
      <span *ngIf="name?.errors?.['required']">Name is required</span>
      <span *ngIf="name?.errors?.['minlength']">Min 3 characters</span>
    </div>
  </div>
  
  <div class="form-group">
    <label>Email</label>
    <input formControlName="email" type="email">
  </div>
  
  <!-- Nested form group -->
  <div formGroupName="address">
    <input formControlName="street" placeholder="Street">
    <input formControlName="city" placeholder="City">
    <input formControlName="zipCode" placeholder="Zip Code">
  </div>
  
  <button type="submit" [disabled]="userForm.invalid">Submit</button>
  
</form>

Import ReactiveFormsModule

Don't forget to import ReactiveFormsModule in your module!

10

HTTP & APIs

Angular's HttpClient module provides a simplified API for HTTP functionality.

HTTP Client Setup

app.module.ts
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule,  // Add this
    ...
  ],
  ...
})
export class AppModule { }

API Service with HTTP Methods

api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, retry, map } from 'rxjs/operators';

interface Post {
  id: number;
  title: string;
  body: string;
  userId: number;
}

@Injectable({ providedIn: 'root' })
export class ApiService {
  
  private baseUrl = 'https://jsonplaceholder.typicode.com';
  
  private httpOptions = {
    headers: new HttpHeaders({
      'Content-Type': 'application/json',
      'Authorization': 'Bearer token'
    })
  };
  
  constructor(private http: HttpClient) {}
  
  // GET - Fetch all posts
  getPosts(): Observable<Post[]> {
    return this.http.get<Post[]>(`${this.baseUrl}/posts`).pipe(
      retry(2),
      catchError(this.handleError)
    );
  }
  
  // GET with query params
  getPostsByUser(userId: number): Observable<Post[]> {
    const params = new HttpParams().set('userId', userId.toString());
    return this.http.get<Post[]>(`${this.baseUrl}/posts`, { params });
  }
  
  // GET single item
  getPost(id: number): Observable<Post> {
    return this.http.get<Post>(`${this.baseUrl}/posts/${id}`);
  }
  
  // POST - Create
  createPost(post: Omit<Post, 'id'>): Observable<Post> {
    return this.http.post<Post>(
      `${this.baseUrl}/posts`,
      post,
      this.httpOptions
    );
  }
  
  // PUT - Update
  updatePost(id: number, post: Partial<Post>): Observable<Post> {
    return this.http.put<Post>(
      `${this.baseUrl}/posts/${id}`,
      post,
      this.httpOptions
    );
  }
  
  // DELETE
  deletePost(id: number): Observable<void> {
    return this.http.delete<void>(`${this.baseUrl}/posts/${id}`);
  }
  
  // Error handling
  private handleError(error: any): Observable<never> {
    console.error('An error occurred:', error);
    return throwError(() => new Error('Something went wrong'));
  }
}

HTTP Interceptors

interceptors/auth.interceptor.ts
import { Injectable } from '@angular/core';
import {
  HttpInterceptor, HttpRequest, HttpHandler,
  HttpEvent, HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  
  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    
    // Get token from storage
    const token = localStorage.getItem('token');
    
    // Clone request and add auth header
    if (token) {
      const authReq = req.clone({
        setHeaders: {
          Authorization: `Bearer ${token}`
        }
      });
      return next.handle(authReq).pipe(
        catchError(this.handleError)
      );
    }
    
    return next.handle(req);
  }
  
  private handleError(error: HttpErrorResponse): Observable<never> {
    if (error.status === 401) {
      // Redirect to login
      console.log('Unauthorized - redirecting to login');
    }
    return throwError(() => error);
  }
}
app.module.ts - Register Interceptor
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './interceptors/auth.interceptor';

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ]
})
export class AppModule { }
11

Pipes

Pipes transform data in templates. Angular provides built-in pipes and allows custom pipe creation.

Built-in Pipes

pipes-example.component.html
<!-- String pipes -->
<p>{{ 'hello world' | uppercase }}</p>  <!-- HELLO WORLD -->
<p>{{ 'HELLO WORLD' | lowercase }}</p>  <!-- hello world -->
<p>{{ 'hello world' | titlecase }}</p>  <!-- Hello World -->

<!-- Date pipe -->
<p>{{ today | date }}</p>                  <!-- Jan 25, 2025 -->
<p>{{ today | date:'short' }}</p>          <!-- 1/25/25, 10:30 AM -->
<p>{{ today | date:'fullDate' }}</p>       <!-- Saturday, January 25, 2025 -->
<p>{{ today | date:'yyyy-MM-dd' }}</p>    <!-- 2025-01-25 -->
<p>{{ today | date:'HH:mm:ss' }}</p>      <!-- 10:30:45 -->

<!-- Number pipes -->
<p>{{ 3.14159 | number:'1.2-2' }}</p>     <!-- 3.14 -->
<p>{{ 1234567 | number }}</p>             <!-- 1,234,567 -->

<!-- Currency pipe -->
<p>{{ 99.99 | currency }}</p>             <!-- $99.99 -->
<p>{{ 99.99 | currency:'EUR' }}</p>       <!-- €99.99 -->
<p>{{ 99.99 | currency:'INR':'symbol' }}</p> <!-- ₹99.99 -->

<!-- Percent pipe -->
<p>{{ 0.25 | percent }}</p>               <!-- 25% -->
<p>{{ 0.2567 | percent:'1.2-2' }}</p>    <!-- 25.67% -->

<!-- JSON pipe (useful for debugging) -->
<pre>{{ user | json }}</pre>

<!-- Slice pipe -->
<p>{{ 'Hello World' | slice:0:5 }}</p>    <!-- Hello -->
<li *ngFor="let item of items | slice:0:5">{{ item }}</li>

<!-- Async pipe (auto subscribes/unsubscribes) -->
<p>{{ user$ | async }}</p>
<div *ngIf="users$ | async as users">
  <p *ngFor="let user of users">{{ user.name }}</p>
</div>

<!-- KeyValue pipe (for objects) -->
<div *ngFor="let item of myObject | keyvalue">
  {{ item.key }}: {{ item.value }}
</div>

Custom Pipe

Terminal
$ ng generate pipe pipes/truncate
pipes/truncate.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'truncate'
})
export class TruncatePipe implements PipeTransform {
  
  transform(
    value: string,
    limit: number = 50,
    ellipsis: string = '...'
  ): string {
    if (!value) return '';
    if (value.length <= limit) return value;
    return value.substring(0, limit) + ellipsis;
  }
}
Usage in template
<!-- Default: 50 chars with ... -->
<p>{{ longText | truncate }}</p>

<!-- Custom limit -->
<p>{{ longText | truncate:100 }}</p>

<!-- Custom limit and ellipsis -->
<p>{{ longText | truncate:30:' [Read More]' }}</p>
pipes/time-ago.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'timeAgo'
})
export class TimeAgoPipe implements PipeTransform {
  
  transform(value: Date | string): string {
    const date = new Date(value);
    const now = new Date();
    const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
    
    if (seconds < 60) return 'Just now';
    if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago`;
    if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`;
    if (seconds < 604800) return `${Math.floor(seconds / 86400)} days ago`;
    
    return date.toLocaleDateString();
  }
}
12

Advanced Topics

RxJS Observables

rxjs-examples.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { 
  Observable, Subject, BehaviorSubject, 
  Subscription, interval, fromEvent, of 
} from 'rxjs';
import { 
  map, filter, debounceTime, distinctUntilChanged,
  switchMap, catchError, takeUntil, tap
} from 'rxjs/operators';

@Component({...})
export class RxjsExamplesComponent implements OnInit, OnDestroy {
  
  private destroy$ = new Subject<void>();
  searchResults$!: Observable<any[]>;
  
  ngOnInit(): void {
    // Example 1: Transform data with map
    of(1, 2, 3, 4, 5).pipe(
      map(x => x * 2),
      filter(x => x > 4)
    ).subscribe(val => console.log(val)); // 6, 8, 10
    
    // Example 2: Search with debounce
    this.searchResults$ = this.searchInput$.pipe(
      debounceTime(300),
      distinctUntilChanged(),
      switchMap(term => this.searchService.search(term)),
      catchError(err => of([]))
    );
    
    // Example 3: Auto-unsubscribe with takeUntil
    interval(1000).pipe(
      takeUntil(this.destroy$)
    ).subscribe(val => console.log(val));
  }
  
  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

State Management with NgRx

Terminal
$ ng add @ngrx/store@latest $ ng add @ngrx/effects@latest $ ng add @ngrx/store-devtools@latest
store/user/user.actions.ts
import { createAction, props } from '@ngrx/store';
import { User } from '../models/user.model';

export const loadUsers = createAction('[User] Load Users');
export const loadUsersSuccess = createAction(
  '[User] Load Users Success',
  props<{ users: User[] }>()
);
export const loadUsersFailure = createAction(
  '[User] Load Users Failure',
  props<{ error: string }>()
);
store/user/user.reducer.ts
import { createReducer, on } from '@ngrx/store';
import * as UserActions from './user.actions';

export interface UserState {
  users: User[];
  loading: boolean;
  error: string | null;
}

const initialState: UserState = {
  users: [],
  loading: false,
  error: null
};

export const userReducer = createReducer(
  initialState,
  on(UserActions.loadUsers, (state) => ({
    ...state,
    loading: true
  })),
  on(UserActions.loadUsersSuccess, (state, { users }) => ({
    ...state,
    users,
    loading: false
  })),
  on(UserActions.loadUsersFailure, (state, { error }) => ({
    ...state,
    error,
    loading: false
  }))
);

Lazy Loading Modules

app-routing.module.ts
const routes: Routes = [
  { path: '', component: HomeComponent },
  
  // Lazy loaded feature modules
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module')
      .then(m => m.AdminModule)
  },
  {
    path: 'products',
    loadChildren: () => import('./products/products.module')
      .then(m => m.ProductsModule)
  },
  
  // Lazy load with preloading strategy
  {
    path: 'dashboard',
    loadChildren: () => import('./dashboard/dashboard.module')
      .then(m => m.DashboardModule),
    data: { preload: true }
  }
];

Production Build & Deployment

Terminal - Build Commands
$ # Development build $ ng build $ # Production build (optimized) $ ng build --configuration=production $ # Build with source maps for debugging $ ng build --source-map $ # Analyze bundle size $ ng build --stats-json $ npx webpack-bundle-analyzer dist/my-app/stats.json $ # Run tests $ ng test $ ng test --code-coverage $ # E2E tests $ ng e2e Build output in: dist/my-app/
environment.prod.ts
export const environment = {
  production: true,
  apiUrl: 'https://api.production.com',
  analyticsKey: 'PROD_ANALYTICS_KEY'
};

Deployment Options

Deploy your Angular app to: Vercel, Netlify, Firebase Hosting, AWS S3 + CloudFront, Azure Static Web Apps, or any static hosting service.

Congratulations!

You've mastered Angular from zero to hero! You're now ready to build powerful, scalable frontend applications. Keep practicing and building projects!