C
h
i
L
L
u
.
.
.

Angular Mastery Guide

Complete guide from components to advanced patterns

Understanding Angular: The Enterprise Framework

Angular is a platform and framework for building single-page client applications using HTML and TypeScript. Developed and maintained by Google, Angular provides a comprehensive solution for building large-scale, enterprise-grade applications with strong typing, dependency injection, and a powerful CLI.

What makes Angular unique is its opinionated, batteries-included approach that provides everything you need out of the box - routing, forms, HTTP client, testing utilities, and more - all integrated seamlessly with TypeScript.

Enterprise Standard: Angular is used by companies like Google, Microsoft, IBM, and Forbes. Its comprehensive nature, strong typing, and enterprise-focused features make it the framework of choice for large teams building complex applications.

1. Angular Basics & Components

Getting Started: Your First Angular App

Angular applications are built using components, modules, and services. The Angular CLI makes it easy to create and manage projects.

// Install Angular CLI
npm install -g @angular/cli

// Create new project
ng new my-app
cd my-app
ng serve

// Basic component structure
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>Welcome to {{title}}!</h1>
    <app-hero-list></app-hero-list>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'My Angular App';
}

// Module definition
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HeroListComponent } from './hero-list.component';

@NgModule({
  declarations: [
    AppComponent,
    HeroListComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

// Main entry point
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

Components: Building Blocks of Angular

Components are the fundamental building blocks of Angular applications. They control a patch of screen called a view.

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { CommonModule } from '@angular/common';

// Hero interface
export interface Hero {
  id: number;
  name: string;
  power: string;
}

// Hero list component
@Component({
  selector: 'app-hero-list',
  standalone: true,
  imports: [CommonModule, HeroDetailComponent],
  template: `
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes"
          [class.selected]="hero === selectedHero"
          (click)="onSelect(hero)">
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
    
    <app-hero-detail 
      [hero]="selectedHero"
      (heroSaved)="onHeroSaved($event)">
    </app-hero-detail>
  `,
  styles: [`
    .heroes {
      margin: 0 0 2em 0;
      list-style-type: none;
      padding: 0;
    }
    .heroes li {
      cursor: pointer;
      position: relative;
      left: 0;
      background-color: #EEE;
      margin: .5em;
      padding: .3em 0;
      border-radius: 4px;
    }
    .heroes li.selected {
      background-color: #CFD8DC;
      color: white;
    }
  `]
})
export class HeroListComponent {
  @Input() heroes: Hero[] = [];
  @Output() heroSelected = new EventEmitter<Hero>();
  
  selectedHero?: Hero;

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
    this.heroSelected.emit(hero);
  }

  onHeroSaved(hero: Hero): void {
    console.log('Hero saved:', hero);
    // Update the hero in the list
    const index = this.heroes.findIndex(h => h.id === hero.id);
    if (index !== -1) {
      this.heroes[index] = hero;
    }
  }
}

// Hero detail component
@Component({
  selector: 'app-hero-detail',
  standalone: true,
  imports: [CommonModule, FormsModule],
  template: `
    <div *ngIf="hero">
      <h2>{{hero.name}} Details</h2>
      <div><span>id: </span>{{hero.id}}</div>
      <div>
        <label for="hero-name">Hero name: </label>
        <input id="hero-name" [(ngModel)]="hero.name" placeholder="name">
      </div>
      <div>
        <label for="hero-power">Power: </label>
        <input id="hero-power" [(ngModel)]="hero.power" placeholder="power">
      </div>
      <button (click)="save()">Save</button>
    </div>
  `
})
export class HeroDetailComponent {
  @Input() hero?: Hero;
  @Output() heroSaved = new EventEmitter<Hero>();

  save(): void {
    if (this.hero) {
      this.heroSaved.emit(this.hero);
    }
  }
}

// Standalone component (Angular 14+)
@Component({
  selector: 'app-standalone-hero',
  standalone: true,
  imports: [CommonModule, FormsModule],
  template: `
    <div class="standalone-hero">
      <h3>{{title}}</h3>
      <p>This is a standalone component!</p>
    </div>
  `
})
export class StandaloneHeroComponent {
  title = 'Standalone Hero Component';
}

// Component lifecycle hooks
@Component({
  selector: 'app-lifecycle-demo',
  template: `
    <div>
      <h3>Lifecycle Demo</h3>
      <button (click)="toggle()">Toggle</button>
      <div *ngIf="showContent">Content to show/hide</div>
    </div>
  `
})
export class LifecycleDemoComponent implements 
  OnInit, OnChanges, OnDestroy, AfterViewInit, AfterContentInit {
  
  @Input() data: any;
  showContent = true;

  constructor() {
    console.log('Constructor called');
  }

  ngOnInit(): void {
    console.log('OnInit - Component initialized');
  }

  ngOnChanges(changes: SimpleChanges): void {
    console.log('OnChanges - Input properties changed', changes);
  }

  ngAfterViewInit(): void {
    console.log('AfterViewInit - View initialized');
  }

  ngAfterContentInit(): void {
    console.log('AfterContentInit - Content projected');
  }

  ngOnDestroy(): void {
    console.log('OnDestroy - Component destroyed');
  }

  toggle(): void {
    this.showContent = !this.showContent;
  }
}

Modules and Application Structure

Angular applications are modular, organized into NgModules that consolidate related code into functional sets.

// Core module for singleton services
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';

import { AuthService } from './auth.service';
import { UserService } from './user.service';
import { throwIfAlreadyLoaded } from './module-import-guard';

@NgModule({
  imports: [
    CommonModule,
    HttpClientModule
  ],
  providers: [
    AuthService,
    UserService
  ]
})
export class CoreModule {
  constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
    throwIfAlreadyLoaded(parentModule, 'CoreModule');
  }
}

// Shared module for common components, directives, pipes
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

import { HighlightDirective } from './highlight.directive';
import { UnlessDirective } from './unless.directive';
import { FilterPipe } from './filter.pipe';
import { LoadingSpinnerComponent } from './loading-spinner.component';

@NgModule({
  imports: [
    CommonModule,
    FormsModule
  ],
  declarations: [
    HighlightDirective,
    UnlessDirective,
    FilterPipe,
    LoadingSpinnerComponent
  ],
  exports: [
    CommonModule,
    FormsModule,
    HighlightDirective,
    UnlessDirective,
    FilterPipe,
    LoadingSpinnerComponent
  ]
})
export class SharedModule { }

// Feature module for heroes functionality
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';

import { HeroListComponent } from './hero-list.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroSearchComponent } from './hero-search.component';
import { HeroService } from './hero.service';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    RouterModule.forChild([
      { path: 'heroes', component: HeroListComponent },
      { path: 'hero/:id', component: HeroDetailComponent }
    ])
  ],
  declarations: [
    HeroListComponent,
    HeroDetailComponent,
    HeroSearchComponent
  ],
  providers: [
    HeroService
  ]
})
export class HeroesModule { }

// Main app module
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';
import { SharedModule } from './shared/shared.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    CoreModule,
    SharedModule,
    AppRoutingModule,
    // Feature modules (lazy loaded)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

// Routing module
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PageNotFoundComponent } from './page-not-found.component';

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { 
    path: 'heroes', 
    loadChildren: () => import('./heroes/heroes.module').then(m => m.HeroesModule)
  },
  { 
    path: 'admin', 
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
  },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      enableTracing: false, // For debugging
      preloadingStrategy: PreloadAllModules
    })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule { }

// Module import guard
export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) {
  if (parentModule) {
    throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`);
  }
}

Directives and Pipes

Directives add behavior to elements in the DOM, while pipes transform displayed values in templates.

// Attribute directive
import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core';

@Directive({
  selector: '[appHighlight]',
  standalone: true
})
export class HighlightDirective {
  @Input() appHighlight = '';
  @Input() defaultColor = '';

  constructor(private el: ElementRef, private renderer: Renderer2) {}

  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.appHighlight || this.defaultColor || 'yellow');
  }

  @HostListener('mouseleave') onMouseLeave() {
    this.highlight('');
  }

  private highlight(color: string) {
    this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', color);
  }
}

// Structural directive
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appUnless]',
  standalone: true
})
export class UnlessDirective {
  private hasView = false;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef
  ) { }

  @Input() set appUnless(condition: boolean) {
    if (!condition && !this.hasView) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (condition && this.hasView) {
      this.viewContainer.clear();
      this.hasView = false;
    }
  }
}

// Custom pipe
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'appFilter',
  standalone: true
})
export class FilterPipe implements PipeTransform {
  transform(items: any[], searchText: string, field: string = 'name'): any[] {
    if (!items) return [];
    if (!searchText) return items;

    searchText = searchText.toLowerCase();
    return items.filter(item => {
      return item[field].toLowerCase().includes(searchText);
    });
  }
}

// Async pipe alternative with loading state
@Pipe({
  name: 'appWithLoading',
  standalone: true
})
export class WithLoadingPipe implements PipeTransform {
  transform(value: any): { data: any, loading: boolean, error: any } {
    if (value instanceof Error) {
      return { data: null, loading: false, error: value };
    }
    if (value === null || value === undefined) {
      return { data: null, loading: true, error: null };
    }
    return { data: value, loading: false, error: null };
  }
}

// Currency pipe with custom formatting
@Pipe({
  name: 'appCustomCurrency',
  standalone: true
})
export class CustomCurrencyPipe implements PipeTransform {
  transform(value: number, currencyCode: string = 'USD', display: 'symbol' | 'code' | 'name' = 'symbol'): string {
    if (value == null) return '';
    
    const formatter = new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: currencyCode,
      currencyDisplay: display,
      minimumFractionDigits: 2,
      maximumFractionDigits: 2
    });

    return formatter.format(value);
  }
}

// Using directives and pipes in templates
@Component({
  selector: 'app-directive-demo',
  standalone: true,
  imports: [CommonModule, HighlightDirective, UnlessDirective, FilterPipe, CustomCurrencyPipe],
  template: `
    <div class="demo-container">
      <!-- Attribute directive -->
      <p [appHighlight]="'lightblue'" [defaultColor]="'violet'">
        Hover over me to see highlight!
      </p>

      <!-- Structural directive -->
      <div *appUnless="isHidden">
        This content is shown unless isHidden is true
      </div>

      <!-- Custom pipe -->
      <input [(ngModel)]="searchText" placeholder="Search heroes">
      <ul>
        <li *ngFor="let hero of heroes | appFilter:searchText">
          {{hero.name}}
        </li>
      </ul>

      <!-- Currency pipe -->
      <p>Price: {{price | appCustomCurrency:'EUR':'symbol'}}</p>
      <p>Price: {{price | appCustomCurrency:'USD':'code'}}</p>

      <!-- With loading pipe -->
      <div *ngIf="data$ | async as result">
        <div *ngIf="result.loading">Loading...</div>
        <div *ngIf="result.error">Error: {{result.error.message}}</div>
        <div *ngIf="result.data">
          Data: {{result.data | json}}
        </div>
      </div>
    </div>
  `
})
export class DirectiveDemoComponent {
  isHidden = false;
  searchText = '';
  price = 1234.56;
  
  heroes = [
    { id: 1, name: 'Superman' },
    { id: 2, name: 'Batman' },
    { id: 3, name: 'Wonder Woman' }
  ];

  data$ = of([1, 2, 3]).pipe(
    delay(1000),
    catchError(err => of(new Error('Failed to load')))
  );
}

2. Templates & Data Binding

Data Binding: Connecting Component and Template

Angular provides several ways to bind data between components and templates, including interpolation, property binding, event binding, and two-way binding.

@Component({
  selector: 'app-data-binding-demo',
  template: `
    <div class="binding-demo">
      <!-- Interpolation -->
      <h2>Hello, {{name}}!</h2>
      <p>1 + 1 = {{1 + 1}}</p>
      <p>Today is {{getCurrentDate() | date:'fullDate'}}</p>

      <!-- Property binding -->
      <img [src]="imageUrl" [alt]="imageAlt" [class.loaded]="isImageLoaded">
      <button [disabled]="isDisabled">Click me</button>
      <div [style.background-color]="backgroundColor"></div>
      <div [ngClass]="{'active': isActive, 'error': hasError}"></div>

      <!-- Attribute binding -->
      <button [attr.aria-label]="ariaLabel">Action</button>
      <div [attr.data-user-id]="userId"></div>

      <!-- Class binding -->
      <div [class.special]="isSpecial">Special content</div>
      <div [class]="classExpression">Dynamic classes</div>

      <!-- Style binding -->
      <div [style.width.px]="width">Width: {{width}}px</div>
      <div [style]="styleExpression">Dynamic styles</div>

      <!-- Event binding -->
      <button (click)="onButtonClick($event)">Click me!</button>
      <input (keyup)="onKeyUp($event)" (keyup.enter)="onEnter()">
      <div (mouseover)="onMouseOver()" (mouseleave)="onMouseLeave()">Hover me</div>

      <!-- Two-way binding -->
      <input [(ngModel)]="userName" placeholder="Enter your name">
      <p>Hello, {{userName}}!</p>

      <select [(ngModel)]="selectedOption">
        <option *ngFor="let option of options" [value]="option">
          {{option}}
        </option>
      </select>

      <!-- Template reference variables -->
      <input #phone placeholder="Phone number">
      <button (click)="callPhone(phone.value)">Call</button>

      <!-- ViewChild access -->
      <div #container>Container content</div>
    </div>
  `
})
export class DataBindingDemoComponent implements AfterViewInit {
  // Interpolation properties
  name = 'Angular Developer';
  
  // Property binding properties
  imageUrl = '/assets/hero.jpg';
  imageAlt = 'Hero image';
  isImageLoaded = true;
  isDisabled = false;
  backgroundColor = 'lightblue';
  isActive = true;
  hasError = false;

  // Attribute binding properties
  ariaLabel = 'Primary action button';
  userId = 'user123';

  // Class binding properties
  isSpecial = true;
  classExpression = 'card highlighted';

  // Style binding properties
  width = 200;
  styleExpression = 'font-size: 16px; color: red;';

  // Event binding properties
  userName = '';
  selectedOption = '';
  options = ['Option 1', 'Option 2', 'Option 3'];

  // Template reference variables
  @ViewChild('container') container!: ElementRef;

  // Methods for event binding
  onButtonClick(event: MouseEvent): void {
    console.log('Button clicked!', event);
    this.isDisabled = !this.isDisabled;
  }

  onKeyUp(event: KeyboardEvent): void {
    console.log('Key pressed:', event.key);
  }

  onEnter(): void {
    console.log('Enter key pressed!');
  }

  onMouseOver(): void {
    this.backgroundColor = 'lightgreen';
  }

  onMouseLeave(): void {
    this.backgroundColor = 'lightblue';
  }

  callPhone(phone: string): void {
    console.log('Calling:', phone);
  }

  getCurrentDate(): Date {
    return new Date();
  }

  ngAfterViewInit(): void {
    // Access template reference variable
    console.log('Container element:', this.container.nativeElement);
  }
}

// Advanced data binding with signals (Angular 16+)
@Component({
  selector: 'app-signals-demo',
  template: `
    <div class="signals-demo">
      <!-- Signal binding -->
      <h2>Counter: {{counter()}}</h2>
      <p>Double: {{doubleCounter()}}</p>
      
      <button (click)="increment()">Increment</button>
      <button (click)="reset()">Reset</button>

      <!-- Computed signals -->
      <div *ngIf="isEven()">Counter is even!</div>
      <div *ngIf="isOdd()">Counter is odd!</div>

      <!-- Effect -->
      <p>Previous value: {{previousValue()}}</p>

      <!-- Signal in forms -->
      <input [value]="name()" (input)="updateName($event)">
      <p>Hello, {{name()}}!</p>

      <!-- Signal arrays -->
      <ul>
        <li *ngFor="let item of items()">
          {{item}}
          <button (click)="removeItem(item)">Remove</button>
        </li>
      </ul>
      <input #newItem placeholder="New item">
      <button (click)="addItem(newItem.value); newItem.value=''">Add</button>
    </div>
  `
})
export class SignalsDemoComponent {
  // Writable signals
  counter = signal(0);
  name = signal('Angular');
  items = signal<string[]>(['Item 1', 'Item 2', 'Item 3']);
  previousValue = signal<number | null>(null);

  // Computed signals
  doubleCounter = computed(() => this.counter() * 2);
  isEven = computed(() => this.counter() % 2 === 0);
  isOdd = computed(() => this.counter() % 2 !== 0);

  constructor() {
    // Effects
    effect(() => {
      const current = this.counter();
      console.log('Counter changed:', current);
      
      // Update previous value
      this.previousValue.set(current - 1);
    });

    effect(() => {
      const currentName = this.name();
      console.log('Name changed:', currentName);
    });
  }

  increment(): void {
    this.counter.update(c => c + 1);
  }

  reset(): void {
    this.counter.set(0);
  }

  updateName(event: Event): void {
    const input = event.target as HTMLInputElement;
    this.name.set(input.value);
  }

  addItem(item: string): void {
    if (item.trim()) {
      this.items.update(items => [...items, item]);
    }
  }

  removeItem(item: string): void {
    this.items.update(items => items.filter(i => i !== item));
  }
}

Template Statements and Expressions

Template expressions and statements allow you to execute logic directly in templates while maintaining separation of concerns.

@Component({
  selector: 'app-template-expressions',
  template: `
    <div class="expressions-demo">
      <!-- Simple expressions -->
      <p>Math: {{ 5 + 3 }}</p>
      <p>Comparison: {{ 10 > 5 }}</p>
      <p>String: {{ 'Hello' + ' ' + 'World' }}</p>

      <!-- Property access -->
      <p>Name: {{ user.name }}</p>
      <p>First item: {{ items[0] }}</p>
      <p>Array length: {{ items.length }}</p>

      <!-- Method calls -->
      <p>Uppercase: {{ 'hello'.toUpperCase() }}</p>
      <p>Formatted date: {{ getFormattedDate() }}</p>
      <p>Full name: {{ getFullName() }}</p>

      <!-- Safe navigation operator -->
      <p>User city: {{ user?.address?.city }}</p>
      <p>First item: {{ items?.[0] }}</p>

      <!-- Template statements -->
      <button (click)="deleteItem(item)">Delete</button>
      <input (keyup.enter)="onEnterPressed($event)">
      <form (ngSubmit)="onSubmit()">...</form>

      <!-- Complex expressions with pipes -->
      <p>Price: {{ totalPrice | currency }}</p>
      <p>Date: {{ currentDate | date:'medium' }}</p>
      <p>Percentage: {{ completionRate | percent:'1.2-2' }}</p>

      <!-- Chaining pipes -->
      <p>Formatted: {{ message | uppercase | slice:0:10 }}</p>

      <!-- Custom template logic -->
      <ng-container *ngIf="isLoggedIn && hasPermission; else noAccess">
        <p>Welcome, authorized user!</p>
      </ng-container>
      
      <ng-template #noAccess>
        <p>Access denied</p>
      </ng-template>

      <!-- Ternary operator -->
      <p [class]="isActive ? 'active' : 'inactive'">
        Status: {{ isActive ? 'Active' : 'Inactive' }}
      </p>

      <!-- Template context -->
      <ng-template #itemTemplate let-item let-index="index">
        <div class="item">
          <span>{{ index + 1 }}. {{ item.name }}</span>
          <button (click)="selectItem(item)">Select</button>
        </div>
      </ng-template>

      <!-- Using template with context -->
      <ng-container 
        *ngTemplateOutlet="itemTemplate; context: { $implicit: currentItem, index: currentIndex }">
      </ng-container>
    </div>
  `
})
export class TemplateExpressionsComponent {
  user = {
    name: 'John Doe',
    address: {
      city: 'New York',
      country: 'USA'
    }
  };

  items = ['Apple', 'Banana', 'Orange'];
  currentItem = { name: 'Current Item' };
  currentIndex = 0;
  
  totalPrice = 99.99;
  currentDate = new Date();
  completionRate = 0.756;
  message = 'Hello Angular World';
  
  isLoggedIn = true;
  hasPermission = true;
  isActive = true;

  getFormattedDate(): string {
    return this.currentDate.toLocaleDateString();
  }

  getFullName(): string {
    return `${this.user.name} from ${this.user.address?.city}`;
  }

  deleteItem(item: any): void {
    console.log('Deleting item:', item);
    // Implementation here
  }

  onEnterPressed(event: Event): void {
    const input = event.target as HTMLInputElement;
    console.log('Enter pressed with value:', input.value);
  }

  onSubmit(): void {
    console.log('Form submitted');
  }

  selectItem(item: any): void {
    console.log('Item selected:', item);
  }
}

// Advanced template patterns
@Component({
  selector: 'app-advanced-templates',
  template: `
    <div class="advanced-templates">
      <!-- Content projection -->
      <app-card>
        <div header>Card Header</div>
        <div body>
          <p>This is the card body content.</p>
          <p>It can contain any HTML elements.</p>
        </div>
        <div footer>Card Footer</div>
      </app-card>

      <!-- Multiple content projection -->
      <app-layout>
        <div sidebar>Sidebar Content</div>
        <div main>Main Content Area</div>
        <div toolbar>Toolbar Actions</div>
      </app-layout>

      <!-- Dynamic templates -->
      <ng-container 
        *ngTemplateOutlet="currentTemplate; context: templateContext">
      </ng-container>

      <button (click)="switchTemplate()">Switch Template</button>

      <!-- Template guards -->
      <div *ngIf="data$ | async as data; loading: loadingTemplate; error: errorTemplate">
        <p>Data loaded: {{ data | json }}</p>
      </div>

      <ng-template #loadingTemplate>
        <p>Loading data...</p>
      </ng-template>

      <ng-template #errorTemplate let-error>
        <p class="error">Error: {{ error.message }}</p>
      </ng-template>
    </div>
  `
})
export class AdvancedTemplatesComponent {
  currentTemplate: TemplateRef<any>;
  templateContext = { message: 'Hello from context!' };

  data$ = of({ id: 1, name: 'Test Data' }).pipe(
    delay(2000)
  );

  constructor(private templateService: TemplateService) {
    this.currentTemplate = this.templateService.getTemplate('default');
  }

  switchTemplate(): void {
    this.currentTemplate = this.templateService.getTemplate('alternative');
  }
}

// Card component with content projection
@Component({
  selector: 'app-card',
  template: `
    <div class="card">
      <div class="card-header">
        <ng-content select="[header]"></ng-content>
      </div>
      <div class="card-body">
        <ng-content select="[body]"></ng-content>
      </div>
      <div class="card-footer">
        <ng-content select="[footer]"></ng-content>
      </div>
    </div>
  `
})
export class CardComponent {}

// Layout component with multiple projection slots
@Component({
  selector: 'app-layout',
  template: `
    <div class="layout">
      <aside class="sidebar">
        <ng-content select="[sidebar]"></ng-content>
      </aside>
      <main class="main-content">
        <ng-content select="[main]"></ng-content>
      </main>
      <div class="toolbar">
        <ng-content select="[toolbar]"></ng-content>
      </div>
    </div>
  `
})
export class LayoutComponent {}

3. Services & Dependency Injection

Services and Dependency Injection

Services are singleton objects that provide functionality across your application. Angular's dependency injection system manages service instantiation and injection.

// Basic service
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root' // Tree-shakable service
})
export class DataService {
  private data: any[] = [];

  constructor() { 
    console.log('DataService created');
  }

  getData(): any
// app/nodejs-complete-guide/page.tsx

export default function NodeJSCompleteGuide() {
  return (
    <div className="min-h-screen bg-gradient-to-br from-green-50 to-emerald-50">
      <div className="max-w-7xl mx-auto px-4 py-8">
        {/* Mobile Header - Only shows on small screens */}
        <header className="lg:hidden text-center mb-8">
          <h1 className="text-3xl font-bold text-gray-800 mb-2">
            Node.js Mastery Guide
          </h1>
          <p className="text-gray-600">
            Complete guide from beginner to advanced
          </p>
          <div className="w-20 h-1 bg-green-500 mx-auto mt-2 rounded"></div>
        </header>

        <div className="flex flex-col md:flex-row gap-6 min-h-screen">
          {/* Left Sidebar - Ads */}
          <aside className="w-full md:w-[20%] order-1 md:order-1">
            <div className="sticky top-6 md:block bg-gray-100 p-4 space-y-6">
              {/* Left Ads Area */}
              <p className="text-center">Left Ads</p>
            </div>
          </aside>

          {/* Main Content */}
          <main className="lg:w-3/5 order-1 lg:order-2">
            {/* Desktop Header - Hidden on mobile */}
            <header className="hidden lg:block text-center mb-12">
              <h1 className="text-4xl md:text-5xl font-bold text-gray-800 mb-4">
                The Complete Node.js Mastery Guide
              </h1>
              <p className="text-xl text-gray-600 max-w-3xl mx-auto">
                A comprehensive journey through Node.js fundamentals to advanced backend development and real-time applications.
              </p>
              <div className="w-24 h-1 bg-green-500 mx-auto mt-4 rounded"></div>
            </header>

            {/* Table of Contents */}
            <nav className="bg-white rounded-lg shadow-md p-6 mb-8">
              <h2 className="text-2xl font-bold text-gray-800 mb-4">
                📚 Table of Contents
              </h2>
              <div className="grid md:grid-cols-2 gap-3">
                <a
                  href="#basics"
                  className="text-green-600 hover:text-green-800 flex items-center"
                >
                  <span className="bg-green-100 text-green-800 px-2 py-1 rounded mr-2 text-sm">
                    1
                  </span>
                  Node.js Basics &amp; Fundamentals
                </a>
                <a
                  href="#modules"
                  className="text-green-600 hover:text-green-800 flex items-center"
                >
                  <span className="bg-green-100 text-green-800 px-2 py-1 rounded mr-2 text-sm">
                    2
                  </span>
                  Modules &amp; NPM
                </a>
                <a
                  href="#async"
                  className="text-green-600 hover:text-green-800 flex items-center"
                >
                  <span className="bg-green-100 text-green-800 px-2 py-1 rounded mr-2 text-sm">
                    3
                  </span>
                  Asynchronous Programming
                </a>
                <a
                  href="#web"
                  className="text-green-600 hover:text-green-800 flex items-center"
                >
                  <span className="bg-green-100 text-green-800 px-2 py-1 rounded mr-2 text-sm">
                    4
                  </span>
                  Web Development
                </a>
                <a
                  href="#database"
                  className="text-green-600 hover:text-green-800 flex items-center"
                >
                  <span className="bg-green-100 text-green-800 px-2 py-1 rounded mr-2 text-sm">
                    5
                  </span>
                  Database Integration
                </a>
                <a
                  href="#advanced"
                  className="text-green-600 hover:text-green-800 flex items-center"
                >
                  <span className="bg-green-100 text-green-800 px-2 py-1 rounded mr-2 text-sm">
                    6
                  </span>
                  Advanced Concepts
                </a>
                <a
                  href="#testing"
                  className="text-green-600 hover:text-green-800 flex items-center"
                >
                  <span className="bg-green-100 text-green-800 px-2 py-1 rounded mr-2 text-sm">
                    7
                  </span>
                  Testing &amp; Debugging
                </a>
                <a
                  href="#deployment"
                  className="text-green-600 hover:text-green-800 flex items-center"
                >
                  <span className="bg-green-100 text-green-800 px-2 py-1 rounded mr-2 text-sm">
                    8
                  </span>
                  Deployment &amp; DevOps
                </a>
              </div>
            </nav>

            {/* Introduction */}
            <section className="bg-white rounded-lg shadow-md p-6 mb-8">
              <h2 className="text-2xl lg:text-3xl font-bold text-gray-800 mb-4">
                Understanding Node.js: The JavaScript Runtime
              </h2>
              <p className="text-gray-700 mb-4 leading-relaxed">
                Node.js is an open-source, cross-platform JavaScript runtime environment that executes JavaScript code outside of a web browser. 
                Created by Ryan Dahl in 2009, Node.js enables developers to use JavaScript for server-side scripting and building scalable network applications.
              </p>
              <p className="text-gray-700 mb-4 leading-relaxed">
                Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications 
                that run across distributed devices. It's built on Chrome's V8 JavaScript engine and uses libuv for handling asynchronous operations.
              </p>
              <div className="bg-yellow-50 border-l-4 border-yellow-500 p-4 my-4">
                <p className="text-yellow-700 text-sm">
                  <strong>Did you know?</strong> Node.js was originally written in 2009 by Ryan Dahl, who was inspired to create it after seeing 
                  a file upload progress bar on Flickr. He realized that the web needed a better way to handle I/O operations, leading to the 
                  creation of the non-blocking, event-driven architecture that made Node.js revolutionary.
                </p>
              </div>
            </section>

            {/* Section 1: Basics */}
            <section
              id="basics"
              className="bg-white rounded-lg shadow-md p-6 mb-8"
            >
              <h2 className="text-2xl lg:text-3xl font-bold text-gray-800 mb-6">
                1. Node.js Basics &amp; Fundamentals
              </h2>

              <div className="space-y-8">
                <ArticleSection
                  title="Getting Started with Node.js"
                  content="Node.js can be installed on various operating systems and provides a powerful runtime for executing JavaScript on the server side. Understanding the basic concepts and setup is crucial."
                  code={\`// Basic Node.js script
// Save as app.js

console.log('Hello, Node.js!');

// Process information
console.log('Node.js version:', process.version);
console.log('Platform:', process.platform);
console.log('Current directory:', process.cwd());

// Command line arguments
console.log('Arguments:', process.argv);

// Simple function
function greet(name) {
    return `Hello, ${name}!`;
}

console.log(greet('World'));

// Running the script:
// node app.js
// node app.js argument1 argument2

// File: calculator.js
// Simple calculator module
function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

function multiply(a, b) {
    return a * b;
}

function divide(a, b) {
    if (b === 0) {
        throw new Error('Division by zero is not allowed');
    }
    return a / b;
}

// Export functions
module.exports = {
    add,
    subtract,
    multiply,
    divide
};

// Using the calculator in another file
// File: main.js
const calculator = require('./calculator');

console.log('5 + 3 =', calculator.add(5, 3));
console.log('10 - 4 =', calculator.subtract(10, 4));
console.log('6 * 7 =', calculator.multiply(6, 7));
console.log('15 / 3 =', calculator.divide(15, 3));

try {
    console.log('10 / 0 =', calculator.divide(10, 0));
} catch (error) {
    console.error('Error:', error.message);
}

Core Modules and Globals

Node.js comes with built-in modules that provide essential functionality. Understanding these core modules and global objects is fundamental to Node.js development.

// File: core-modules.js

// File System Module
const fs = require('fs');
const path = require('path');

// Path operations
console.log('Current file:', __filename);
console.log('Current directory:', __dirname);
console.log('Path separator:', path.sep);
console.log('File extension:', path.extname(__filename));
console.log('Base name:', path.basename(__filename));
console.log('Directory name:', path.dirname(__filename));

// File operations
const filePath = './example.txt';

// Write file
fs.writeFileSync(filePath, 'Hello, Node.js File System!\nThis is a second line.');

// Read file
const content = fs.readFileSync(filePath, 'utf8');
console.log('File content:', content);

// Append to file
fs.appendFileSync(filePath, '\nAppended content!');

// Check if file exists
if (fs.existsSync(filePath)) {
    console.log('File exists');
}

// Read file asynchronously
fs.readFile(filePath, 'utf8', (err, data) => {
    if (err) {
        console.error('Error reading file:', err);
        return;
    }
    console.log('Async file content:', data);
});

// OS Module
const os = require('os');

console.log('OS Platform:', os.platform());
console.log('OS Architecture:', os.arch());
console.log('CPU Cores:', os.cpus().length);
console.log('Total Memory:', (os.totalmem() / 1024 / 1024 / 1024).toFixed(2), 'GB');
console.log('Free Memory:', (os.freemem() / 1024 / 1024 / 1024).toFixed(2), 'GB');
console.log('Home Directory:', os.homedir());
console.log('Uptime:', (os.uptime() / 60 / 60).toFixed(2), 'hours');

// URL Module
const url = require('url');

const exampleUrl = 'https://example.com:8080/path/name?query=string#hash';
const parsedUrl = url.parse(exampleUrl, true);

console.log('Protocol:', parsedUrl.protocol);
console.log('Host:', parsedUrl.host);
console.log('Path:', parsedUrl.pathname);
console.log('Query:', parsedUrl.query);
console.log('Hash:', parsedUrl.hash);

// Util Module
const util = require('util');

// Promisify callback-based functions
const readFileAsync = util.promisify(fs.readFile);

// Use with async/await
async function readFileExample() {
    try {
        const data = await readFileAsync(filePath, 'utf8');
        console.log('Promisified file read:', data);
    } catch (error) {
        console.error('Error:', error);
    }
}

readFileExample();

// Format strings
const formatted = util.format('Hello %s, you have %d messages', 'John', 5);
console.log('Formatted:', formatted);

// Events Module
const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

myEmitter.on('event', (data) => {
    console.log('Event received with data:', data);
});

myEmitter.on('error', (error) => {
    console.error('Error occurred:', error);
});

// Emit events
myEmitter.emit('event', { message: 'Hello Event!' });
myEmitter.emit('error', new Error('Something went wrong'));

Working with File System

Node.js provides comprehensive file system operations through the fs module, supporting both synchronous and asynchronous file operations.

// File: file-operations.js
const fs = require('fs').promises; // Using promises API
const path = require('path');

class FileManager {
    constructor(basePath = './data') {
        this.basePath = basePath;
    }

    // Ensure directory exists
    async ensureDirectory() {
        try {
            await fs.access(this.basePath);
        } catch (error) {
            await fs.mkdir(this.basePath, { recursive: true });
            console.log('Directory created:', this.basePath);
        }
    }

    // Create file with content
    async createFile(filename, content) {
        await this.ensureDirectory();
        const filePath = path.join(this.basePath, filename);
        
        await fs.writeFile(filePath, content);
        console.log('File created:', filePath);
        return filePath;
    }

    // Read file content
    async readFile(filename) {
        const filePath = path.join(this.basePath, filename);
        
        try {
            const content = await fs.readFile(filePath, 'utf8');
            console.log('File read:', filePath);
            return content;
        } catch (error) {
            throw new Error(`File not found: ${filePath}`);
        }
    }

    // Update file (append)
    async appendToFile(filename, content) {
        const filePath = path.join(this.basePath, filename);
        
        try {
            await fs.appendFile(filePath, content);
            console.log('Content appended to:', filePath);
        } catch (error) {
            throw new Error(`Cannot append to file: ${filePath}`);
        }
    }

    // Delete file
    async deleteFile(filename) {
        const filePath = path.join(this.basePath, filename);
        
        try {
            await fs.unlink(filePath);
            console.log('File deleted:', filePath);
        } catch (error) {
            throw new Error(`Cannot delete file: ${filePath}`);
        }
    }

    // List files in directory
    async listFiles() {
        await this.ensureDirectory();
        
        try {
            const files = await fs.readdir(this.basePath);
            console.log('Files in directory:', files);
            return files;
        } catch (error) {
            throw new Error(`Cannot read directory: ${this.basePath}`);
        }
    }

    // Get file information
    async getFileInfo(filename) {
        const filePath = path.join(this.basePath, filename);
        
        try {
            const stats = await fs.stat(filePath);
            return {
                filename,
                size: stats.size,
                created: stats.birthtime,
                modified: stats.mtime,
                isDirectory: stats.isDirectory(),
                isFile: stats.isFile()
            };
        } catch (error) {
            throw new Error(`Cannot get file info: ${filePath}`);
        }
    }

    // Copy file
    async copyFile(sourceFilename, destFilename) {
        const sourcePath = path.join(this.basePath, sourceFilename);
        const destPath = path.join(this.basePath, destFilename);
        
        try {
            await fs.copyFile(sourcePath, destPath);
            console.log('File copied:', sourcePath, '->', destPath);
        } catch (error) {
            throw new Error(`Cannot copy file: ${sourcePath} to ${destPath}`);
        }
    }

    // Watch file for changes
    async watchFile(filename, callback) {
        const filePath = path.join(this.basePath, filename);
        
        try {
            fs.watch(filePath, (eventType) => {
                console.log(`File ${filename} changed: ${eventType}`);
                callback(eventType, filename);
            });
            console.log('Watching file:', filePath);
        } catch (error) {
            throw new Error(`Cannot watch file: ${filePath}`);
        }
    }
}

// Usage example
async function demonstrateFileOperations() {
    const fileManager = new FileManager();

    try {
        // Create and manipulate files
        await fileManager.createFile('test.txt', 'Initial content\n');
        await fileManager.appendToFile('test.txt', 'Additional content\n');
        
        const content = await fileManager.readFile('test.txt');
        console.log('Current content:', content);

        const fileInfo = await fileManager.getFileInfo('test.txt');
        console.log('File info:', fileInfo);

        await fileManager.copyFile('test.txt', 'test-backup.txt');
        await fileManager.listFiles();

        // Watch for changes
        await fileManager.watchFile('test.txt', (eventType, filename) => {
            console.log(`Detected ${eventType} on ${filename}`);
        });

        // Simulate change after 2 seconds
        setTimeout(async () => {
            await fileManager.appendToFile('test.txt', 'Change from watcher\n');
        }, 2000);

        // Clean up after 5 seconds
        setTimeout(async () => {
            await fileManager.deleteFile('test.txt');
            await fileManager.deleteFile('test-backup.txt');
        }, 5000);

    } catch (error) {
        console.error('Error:', error.message);
    }
}

// demonstrateFileOperations();

// Streams for large files
const { createReadStream, createWriteStream } = require('fs');

function copyLargeFile(source, destination) {
    return new Promise((resolve, reject) => {
        const readStream = createReadStream(source);
        const writeStream = createWriteStream(destination);

        readStream.on('error', reject);
        writeStream.on('error', reject);
        writeStream.on('finish', resolve);

        readStream.pipe(writeStream);
    });
}

// Example: Process large CSV file
async function processLargeFile() {
    const readline = require('readline');
    const fs = require('fs');

    const fileStream = fs.createReadStream('./large-file.csv');
    const rl = readline.createInterface({
        input: fileStream,
        crlfDelay: Infinity
    });

    let lineCount = 0;
    
    for await (const line of rl) {
        lineCount++;
        // Process each line
        if (lineCount % 1000 === 0) {
            console.log(`Processed ${lineCount} lines`);
        }
    }

    console.log(`Total lines processed: ${lineCount}`);
}

2. Modules & NPM