Phase 3 — Components Deep Dive

Chapter 1 — What We Are Going to Learn and Why


In Phase 2 you learned what a component is and how to create one. You know that a component has a TypeScript file, an HTML template, and a CSS file. You know how to generate components and wire them together.

But so far you have only scratched the surface. A component is capable of so much more than just displaying some static data.

Think about any real application. A product card on an e-commerce site receives product data from its parent and displays it. A like button sends an event back up to the parent when clicked. A modal dialog only runs certain code when it first appears and cleans up when it disappears. A form validates user input and reacts to every keystroke.

All of this is possible because of what we are covering in this phase — the deeper mechanics of how components work.

Here is what we will cover:

We will start with lifecycle hooks — Angular gives every component a predictable life cycle, from the moment it is created to the moment it is destroyed. You can hook into specific moments of this lifecycle and run your own code. This is how you load data when a page opens, react to changes, and clean up resources when a component is removed.

Then we will cover @Input() — the mechanism for passing data from a parent component down to a child component. This is how you build reusable components that can display different data each time they are used.

Then @Output() and EventEmitter — the mechanism for a child component to communicate back up to its parent. The child says "something happened" and the parent listens and reacts.

Then @ViewChild — a way for a parent to directly access and talk to a child component from TypeScript code.

Then content projection with ng-content — a powerful feature that lets you build wrapper components that can accept and display HTML passed in from outside.

Then ViewEncapsulation — how Angular keeps your component's CSS styles scoped and isolated.

Then change detection — how Angular knows when data has changed and when it needs to update the screen.

By the end of this phase you will understand components at a deep level. Let's start.


Chapter 2 — Lifecycle Hooks


2.1 — What is a Lifecycle?

Every Angular component has a lifecycle. A lifecycle is simply the sequence of events that happen from the moment a component is created to the moment it is destroyed.

Think of it like a human life:

You are born → you grow up → you go through changes → eventually you pass away.

A component has something similar:

Component is created → its template is rendered → its data changes → eventually it is removed from the screen.

Angular provides lifecycle hooks — special methods you can add to your component class that Angular automatically calls at specific moments in this lifecycle. They are called "hooks" because you are "hooking into" a specific moment to run your own code.

This is incredibly useful. For example:

  • You want to load data from an API when a component first appears on screen. You hook into the "component just initialized" moment.
  • You want to react every time a specific piece of data changes. You hook into the "inputs just changed" moment.
  • You want to cancel an ongoing subscription when a component is removed. You hook into the "component is about to be destroyed" moment.

Without lifecycle hooks, you would have no reliable way to do any of this.


2.2 — The 8 Lifecycle Hooks

Angular has 8 lifecycle hooks in total. They fire in a specific order. Let's go through each one.


ngOnChanges

This hook fires when an @Input() property value changes. It is the first hook to fire, even before ngOnInit. It fires once when the component first loads (with the initial input values) and then again every time any input value changes after that.

It receives a SimpleChanges object that tells you what changed, what the previous value was, and what the current value is.

import { Component, OnChanges, SimpleChanges, Input } from '@angular/core';

@Component({
  selector: 'app-product-card',
  imports: [],
  templateUrl: './product-card.html',
  styleUrl: './product-card.css'
})
export class ProductCard implements OnChanges {

  @Input() productName: string = '';
  @Input() price: number = 0;

  ngOnChanges(changes: SimpleChanges): void {
    console.log('Something changed!', changes);

    if (changes['productName']) {
      console.log('Previous name:', changes['productName'].previousValue);
      console.log('Current name:', changes['productName'].currentValue);
      console.log('Is this the first change?', changes['productName'].firstChange);
    }
  }
}

Notice implements OnChanges after the class name. This is a TypeScript interface. It is not required to make the hook work, but it is a best practice because TypeScript will warn you if you spell the method name wrong.

When to use it: When you receive data through @Input() and you need to react to it changing. For example, re-running a calculation when a new value comes in.


ngOnInit

This is the most important and most commonly used lifecycle hook. It fires once, after Angular has finished setting up the component and initialized all its @Input() properties. It only ever fires once in the component's lifetime.

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-user-profile',
  imports: [],
  templateUrl: './user-profile.html',
  styleUrl: './user-profile.css'
})
export class UserProfile implements OnInit {

  userData: any = null;

  ngOnInit(): void {
    console.log('Component is ready!');
    // This is where you load data from an API
    // This is where you run any one-time setup code
    this.loadUserData();
  }

  loadUserData(): void {
    // we will use a real API call in Phase 8
    // for now, simulating it:
    this.userData = {
      name: 'Rahul Sharma',
      email: 'rahul@example.com',
      role: 'Developer'
    };
  }
}

You might wonder — why not just put initialization code in the constructor? The constructor is Angular's very first step when creating a component, but at that point Angular has not yet set up the @Input() properties. ngOnInit fires after everything is ready, so it is always the right place for initialization logic.

When to use it: Almost everything you want to run once when a component loads. Loading data from an API, setting up initial values, starting a timer, subscribing to a data stream.


ngDoCheck

This hook fires on every single change detection cycle — which means it can fire very frequently. Angular runs change detection whenever something happens in the app: a click, a keypress, an HTTP response, a timer firing. Every single time, ngDoCheck fires.

import { Component, DoCheck } from '@angular/core';

@Component({
  selector: 'app-monitor',
  imports: [],
  template: `<p>Monitoring changes</p>`
})
export class Monitor implements DoCheck {

  ngDoCheck(): void {
    // This fires extremely often — be careful
    // Only put very lightweight code here
    console.log('Change detection cycle ran');
  }
}

When to use it: Only when you need to manually detect a change that Angular cannot detect on its own — for example, changes deep inside an object or array. This is an advanced use case. Most beginners will rarely need this.


ngAfterContentInit

Before explaining this hook, you need to understand content projection. Content projection is when you pass HTML content into a component from outside — using ng-content. We cover this in detail in Chapter 5 of this phase.

ngAfterContentInit fires once after Angular has projected any external content into the component's template.

import { Component, AfterContentInit, ContentChild } from '@angular/core';

@Component({
  selector: 'app-card',
  imports: [],
  template: `
    <div class="card">
      <ng-content></ng-content>
    </div>
  `
})
export class Card implements AfterContentInit {

  ngAfterContentInit(): void {
    console.log('Projected content is ready');
    // Now you can safely interact with projected content
  }
}

When to use it: When you need to do something after projected content has been inserted into your component.


ngAfterContentChecked

This fires after Angular checks the projected content for changes. It fires once after ngAfterContentInit and then again after every subsequent change detection cycle.

import { Component, AfterContentChecked } from '@angular/core';

@Component({ selector: 'app-card', imports: [], template: `<ng-content></ng-content>` })
export class Card implements AfterContentChecked {

  ngAfterContentChecked(): void {
    console.log('Projected content was checked for changes');
  }
}

When to use it: Rarely. Only when you specifically need to react after Angular has verified that projected content has not changed. Most apps will never need this.


ngAfterViewInit

This fires once after Angular has fully rendered the component's own template — meaning all child components in the template are fully initialized and accessible.

This is important because before this hook fires, you cannot safely access child components using @ViewChild. After this hook fires, everything is ready.

import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'app-dashboard',
  imports: [],
  template: `
    <canvas #myChart></canvas>
  `
})
export class Dashboard implements AfterViewInit {

  @ViewChild('myChart') chartCanvas!: ElementRef;

  ngAfterViewInit(): void {
    // The template is fully rendered here
    // You can now safely access @ViewChild references
    console.log('Canvas element:', this.chartCanvas.nativeElement);
    // Safe to initialize a chart library here
  }
}

When to use it: When you need to work with @ViewChild references, or initialize a third-party library (like a chart library or a map) that needs a DOM element to attach to.


ngAfterViewChecked

This fires after Angular has checked the component's view (its own template and all child components) for changes. It fires once after ngAfterViewInit and then again after every change detection cycle.

import { Component, AfterViewChecked } from '@angular/core';

@Component({ selector: 'app-panel', imports: [], template: `<p>Panel</p>` })
export class Panel implements AfterViewChecked {

  ngAfterViewChecked(): void {
    // Fires very frequently — use with caution
    console.log('View was checked');
  }
}

When to use it: Very rarely. Only when you need to react after Angular has verified the view has not changed. Be careful with this one — because it fires so often, putting heavy code here will slow down your app.


ngOnDestroy

This fires just before Angular removes the component from the screen and destroys it. This is your chance to clean up anything the component was doing.

What kind of cleanup? Unsubscribing from data streams, clearing timers, removing event listeners. If you do not clean up these things, they continue running in the background even after the component is gone — this is called a memory leak. ngOnDestroy is how you prevent it.

import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-timer',
  imports: [],
  template: `<p>Time: {{ seconds }} seconds</p>`
})
export class Timer implements OnInit, OnDestroy {

  seconds: number = 0;
  private timerInterval: any;

  ngOnInit(): void {
    // Start a timer when the component appears
    this.timerInterval = setInterval(() => {
      this.seconds++;
    }, 1000);
  }

  ngOnDestroy(): void {
    // Clean up the timer when the component is removed
    // Without this, the timer would keep running forever
    clearInterval(this.timerInterval);
    console.log('Timer cleaned up!');
  }
}

When to use it: Always use this to clean up any ongoing work — timers, subscriptions, event listeners. If your ngOnInit starts something, your ngOnDestroy should stop it.


2.3 — The Lifecycle Order

Here is the exact order in which these hooks fire:

Component Created
      ↓
ngOnChanges         ← fires first (only if @Input() exists)
      ↓
ngOnInit            ← fires once after initialization
      ↓
ngDoCheck           ← fires on every change detection cycle
      ↓
ngAfterContentInit  ← fires once after content projection
      ↓
ngAfterContentChecked ← fires after projected content is checked
      ↓
ngAfterViewInit     ← fires once after view is rendered
      ↓
ngAfterViewChecked  ← fires after view is checked
      ↓
  (repeating cycle of DoCheck, ContentChecked, ViewChecked
   on every subsequent change detection run)
      ↓
ngOnDestroy         ← fires just before component is removed

You do not need to memorize all 8. In your day-to-day Angular work, you will use three the most: ngOnInit, ngOnDestroy, and ngOnChanges. The others are there for specific advanced scenarios.


2.4 — A Practical Lifecycle Example

Let's build a component that demonstrates the three most used hooks working together. This component will show a countdown timer that starts when it appears, reacts when its starting value changes, and cleans up when it disappears.

src/app/countdown/countdown.ts:

import { Component, Input, OnInit, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-countdown',
  imports: [],
  templateUrl: './countdown.html',
  styleUrl: './countdown.css'
})
export class Countdown implements OnInit, OnChanges, OnDestroy {

  @Input() startFrom: number = 10;

  currentCount: number = 0;
  message: string = '';
  private interval: any;

  ngOnChanges(changes: SimpleChanges): void {
    // Fires when startFrom input value changes
    if (changes['startFrom']) {
      console.log('startFrom changed to:', this.startFrom);
      this.currentCount = this.startFrom;
    }
  }

  ngOnInit(): void {
    // Fires once when component is ready
    console.log('Countdown component initialized');
    this.currentCount = this.startFrom;
    this.startCountdown();
  }

  startCountdown(): void {
    this.interval = setInterval(() => {
      if (this.currentCount > 0) {
        this.currentCount--;
      } else {
        this.message = 'Time is up!';
        clearInterval(this.interval);
      }
    }, 1000);
  }

  ngOnDestroy(): void {
    // Fires just before component is removed
    // Cleans up the interval to prevent memory leaks
    if (this.interval) {
      clearInterval(this.interval);
    }
    console.log('Countdown component destroyed, interval cleared');
  }
}

src/app/countdown/countdown.html:

<div class="countdown">
  @if (message) {
    <p class="message">{{ message }}</p>
  } @else {
    <p class="count">{{ currentCount }}</p>
  }
</div>

src/app/countdown/countdown.css:

.countdown {
  text-align: center;
  padding: 24px;
}

.count {
  font-size: 72px;
  font-weight: 700;
  color: #64ffda;
}

.message {
  font-size: 24px;
  color: #e94560;
}

Notice @if in the template — this is Angular's built-in conditional syntax. If message has a value, show the message. Otherwise, show the count. We cover @if deeply in Phase 4.


Chapter 3 — @Input() — Passing Data from Parent to Child


3.1 — Why @Input() Exists

Imagine you are building a product listing page. You have a ProductCard component that displays one product's name, price, and image. Your page has 20 products, so you need to show 20 product cards.

The naive way would be to create 20 different components, one for each product. That is obviously terrible. The right way is to create ONE ProductCard component and pass different product data to it each time you use it.

This is exactly what @Input() enables. It lets a parent component pass data down to a child component. The child component declares what data it can receive using @Input(), and the parent provides that data when it uses the child in its template.

Think of it like a function with parameters. When you call a function, you pass arguments. When you use a component, you pass @Input() values.


3.2 — Creating an @Input() Property

In the child component, you import Input from Angular and use it as a decorator on a property:

src/app/product-card/product-card.ts:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-product-card',
  imports: [],
  templateUrl: './product-card.html',
  styleUrl: './product-card.css'
})
export class ProductCard {

  @Input() productName: string = '';
  @Input() price: number = 0;
  @Input() category: string = '';
  @Input() inStock: boolean = true;
}

The @Input() decorator on a property tells Angular: "this property's value should come from outside — from the parent component that uses me."

The default values after = are used when no value is passed in. Always provide sensible defaults.


3.3 — Passing Data from the Parent

In the parent component, you use the child's selector as a tag and pass values using property binding — square brackets around the property name:

src/app/product-list/product-list.ts:

import { Component } from '@angular/core';
import { ProductCard } from '../product-card/product-card';

@Component({
  selector: 'app-product-list',
  imports: [ProductCard],
  templateUrl: './product-list.html',
  styleUrl: './product-list.css'
})
export class ProductList {

  products = [
    { name: 'Laptop Pro', price: 75000, category: 'Electronics', inStock: true },
    { name: 'Wireless Mouse', price: 1500, category: 'Accessories', inStock: true },
    { name: 'Desk Chair', price: 12000, category: 'Furniture', inStock: false }
  ];
}

src/app/product-list/product-list.html:

<div class="product-list">
  @for (product of products; track product.name) {
    <app-product-card
      [productName]="product.name"
      [price]="product.price"
      [category]="product.category"
      [inStock]="product.inStock">
    </app-product-card>
  }
</div>

The square brackets [productName] tell Angular: "evaluate this as a TypeScript expression, not a plain string." So [productName]="product.name" passes the actual value of product.name into the child's productName input.

Without square brackets: productName="product.name" — this passes the literal string "product.name". Always use square brackets when passing dynamic values.

Now let's build out the product card template:

src/app/product-card/product-card.html:

<div class="card">
  <div class="card-body">
    <span class="category">{{ category }}</span>
    <h3 class="name">{{ productName }}</h3>
    <p class="price">₹{{ price.toLocaleString() }}</p>

    @if (inStock) {
      <span class="badge in-stock">In Stock</span>
    } @else {
      <span class="badge out-of-stock">Out of Stock</span>
    }

    <button [disabled]="!inStock">Add to Cart</button>
  </div>
</div>

src/app/product-card/product-card.css:

.card {
  background: white;
  border-radius: 12px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.08);
  overflow: hidden;
  transition: transform 0.2s;
}

.card:hover {
  transform: translateY(-4px);
}

.card-body {
  padding: 20px;
}

.category {
  font-size: 12px;
  color: #888;
  text-transform: uppercase;
  letter-spacing: 1px;
}

.name {
  font-size: 18px;
  font-weight: 600;
  color: #1a1a2e;
  margin: 8px 0;
}

.price {
  font-size: 22px;
  font-weight: 700;
  color: #0070f3;
  margin-bottom: 12px;
}

.badge {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
  margin-bottom: 16px;
}

.in-stock {
  background: #d4edda;
  color: #155724;
}

.out-of-stock {
  background: #f8d7da;
  color: #721c24;
}

button {
  width: 100%;
  padding: 10px;
  background: #0070f3;
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 14px;
  cursor: pointer;
  transition: background 0.2s;
}

button:disabled {
  background: #ccc;
  cursor: not-allowed;
}

Now when the parent renders three product cards, each one displays different data — all from the same single component. The ProductCard component has no idea where its data comes from. It just knows it receives productName, price, category, and inStock through @Input() and displays them.


3.4 — Required Inputs

If a property must always be provided and there is no sensible default, you can mark it as required. Angular will throw an error at compile time if you forget to pass it:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-user-avatar',
  imports: [],
  template: `<img [src]="imageUrl" [alt]="userName">`
})
export class UserAvatar {

  @Input({ required: true }) userName!: string;
  @Input({ required: true }) imageUrl!: string;
}

The ! after the property name is a TypeScript non-null assertion. It tells TypeScript "I know this might look uninitialized but it will always have a value at runtime." When you mark an input as required: true, it is safe to use ! because Angular guarantees the value will be provided.


3.5 — Input with an Alias

Sometimes you want the property name used in TypeScript to be different from the name used in the template. You can do this with an alias:

@Input({ alias: 'color' }) buttonColor: string = 'blue';

Now in the parent template you use [color]="'red'", but inside the child component TypeScript code you access it as this.buttonColor. This is useful when the external API name and the internal implementation name should be different.


Chapter 4 — @Output() and EventEmitter — Child to Parent Communication


4.1 — Why @Output() Exists

@Input() lets data flow from parent to child. But what about the other direction?

Imagine your ProductCard component has an "Add to Cart" button. When the user clicks it, the parent component needs to know about it — it needs to update the cart count, show a notification, maybe send a request to the server. The child component (ProductCard) cannot do all of this itself. It needs to tell the parent "the button was clicked."

This is what @Output() is for. A child component can emit events that the parent listens to. It is like a child calling out to a parent — "something happened here, you should know about it."


4.2 — Creating an @Output()

In the child component, you import Output and EventEmitter from Angular:

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

@Component({
  selector: 'app-product-card',
  imports: [],
  templateUrl: './product-card.html',
  styleUrl: './product-card.css'
})
export class ProductCard {

  @Input() productName: string = '';
  @Input() price: number = 0;
  @Input() inStock: boolean = true;

  @Output() addedToCart = new EventEmitter<string>();
  // The <string> means this event will carry a string value when it fires

  onAddToCart(): void {
    // Emit the event, passing the product name as the payload
    this.addedToCart.emit(this.productName);
  }
}

EventEmitter<string> is a generic — the type inside <> is the type of data this event will carry when it fires. You can pass any type: a string, a number, an object, or nothing at all with EventEmitter<void>.

Update the template to call onAddToCart() when the button is clicked:

product-card.html:

<div class="card">
  <h3>{{ productName }}</h3>
  <p>₹{{ price }}</p>
  <button (click)="onAddToCart()" [disabled]="!inStock">
    Add to Cart
  </button>
</div>

(click)="onAddToCart()" is Angular's event binding syntax. The parentheses () around click mean "listen to this DOM event." When the button is clicked, call onAddToCart(). We cover event binding deeply in Phase 4.


4.3 — Listening to the Event in the Parent

In the parent component, you listen to the child's event using event binding — but instead of a DOM event like click, you use the @Output() property name:

product-list.ts:

import { Component } from '@angular/core';
import { ProductCard } from '../product-card/product-card';

@Component({
  selector: 'app-product-list',
  imports: [ProductCard],
  templateUrl: './product-list.html',
  styleUrl: './product-list.css'
})
export class ProductList {

  products = [
    { name: 'Laptop Pro', price: 75000, inStock: true },
    { name: 'Wireless Mouse', price: 1500, inStock: true },
    { name: 'Desk Chair', price: 12000, inStock: false }
  ];

  cartItems: string[] = [];
  notification: string = '';

  onProductAddedToCart(productName: string): void {
    this.cartItems.push(productName);
    this.notification = `${productName} added to cart! Cart has ${this.cartItems.length} item(s).`;
    console.log('Cart:', this.cartItems);
  }
}

product-list.html:

<div>
  @if (notification) {
    <div class="notification">{{ notification }}</div>
  }

  <div class="product-grid">
    @for (product of products; track product.name) {
      <app-product-card
        [productName]="product.name"
        [price]="product.price"
        [inStock]="product.inStock"
        (addedToCart)="onProductAddedToCart($event)">
      </app-product-card>
    }
  </div>
</div>

(addedToCart)="onProductAddedToCart($event)" — the parentheses around addedToCart mean "listen to this event." $event is a special Angular variable that contains whatever data was emitted — in this case, the product name string that was passed to .emit().

When the user clicks "Add to Cart" on a product card:

  1. The button triggers (click) in the child template
  2. onAddToCart() runs in the child TypeScript
  3. this.addedToCart.emit(this.productName) fires the event with the product name
  4. Angular passes this to the parent's (addedToCart) listener
  5. onProductAddedToCart($event) runs in the parent TypeScript
  6. The cart updates and the notification appears

This is the complete parent-child communication pattern in Angular.


4.4 — Emitting Complex Objects

You are not limited to emitting simple strings. You can emit any data type — including objects:

interface CartItem {
  productName: string;
  price: number;
  quantity: number;
}

@Output() addedToCart = new EventEmitter<CartItem>();

onAddToCart(): void {
  this.addedToCart.emit({
    productName: this.productName,
    price: this.price,
    quantity: 1
  });
}

And in the parent:

onProductAddedToCart(item: CartItem): void {
  console.log('Added to cart:', item.productName, 'at price', item.price);
}

Chapter 5 — @ViewChild — Accessing Child Components from the Parent


5.1 — What is @ViewChild?

@Input() and @Output() are how components communicate during the normal data flow — parent passes data down, child emits events up. But sometimes you need to go further. Sometimes the parent needs to directly call a method on a child component, or access a specific HTML element in the template.

@ViewChild gives you a direct reference to a child component or a DOM element from within the parent TypeScript code.


5.2 — Accessing a Child Component with @ViewChild

Let's say you have a VideoPlayer component with a play() and pause() method. The parent has a Play button and a Pause button that should control the video player.

src/app/video-player/video-player.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-video-player',
  imports: [],
  template: `
    <div class="player">
      <div class="screen">{{ status }}</div>
    </div>
  `,
  styles: [`
    .player { background: #000; padding: 40px; border-radius: 8px; }
    .screen { color: #64ffda; text-align: center; font-size: 18px; }
  `]
})
export class VideoPlayer {

  status: string = 'Stopped';
  isPlaying: boolean = false;

  play(): void {
    this.isPlaying = true;
    this.status = '▶ Playing...';
    console.log('Video started playing');
  }

  pause(): void {
    this.isPlaying = false;
    this.status = '⏸ Paused';
    console.log('Video paused');
  }

  stop(): void {
    this.isPlaying = false;
    this.status = '⏹ Stopped';
  }
}

Now the parent uses @ViewChild to get a direct reference to this component:

src/app/media-controls/media-controls.ts:

import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { VideoPlayer } from '../video-player/video-player';

@Component({
  selector: 'app-media-controls',
  imports: [VideoPlayer],
  templateUrl: './media-controls.html',
  styleUrl: './media-controls.css'
})
export class MediaControls implements AfterViewInit {

  @ViewChild(VideoPlayer) videoPlayer!: VideoPlayer;
  // This gives us a direct reference to the VideoPlayer component instance

  ngAfterViewInit(): void {
    // @ViewChild is only available from ngAfterViewInit onwards
    // Before this hook, the view is not rendered yet, so the reference would be undefined
    console.log('VideoPlayer component reference:', this.videoPlayer);
  }

  onPlayClick(): void {
    this.videoPlayer.play(); // directly calling the child's method
  }

  onPauseClick(): void {
    this.videoPlayer.pause();
  }

  onStopClick(): void {
    this.videoPlayer.stop();
  }
}

src/app/media-controls/media-controls.html:

<div class="media-controls">
  <app-video-player></app-video-player>

  <div class="controls">
    <button (click)="onPlayClick()">▶ Play</button>
    <button (click)="onPauseClick()">⏸ Pause</button>
    <button (click)="onStopClick()">⏹ Stop</button>
  </div>
</div>

@ViewChild(VideoPlayer) tells Angular: "find the first instance of VideoPlayer in my template and give me a reference to it." Now this.videoPlayer is the actual VideoPlayer instance, and you can call any of its public methods or access its properties directly.

The ! after videoPlayer!: VideoPlayer is the non-null assertion again — you are telling TypeScript this will never be null at the point you use it (because you only use it after ngAfterViewInit).


5.3 — Accessing a DOM Element with @ViewChild

You can also use @ViewChild to access a raw HTML element using a template reference variable. A template reference variable is a name you give to an HTML element using #:

import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-auto-focus',
  imports: [],
  template: `
    <input #searchInput type="text" placeholder="Search...">
    <button (click)="focusInput()">Focus Input</button>
  `
})
export class AutoFocus implements AfterViewInit {

  @ViewChild('searchInput') searchInput!: ElementRef;
  // 'searchInput' matches the #searchInput in the template

  ngAfterViewInit(): void {
    // Automatically focus the input when component loads
    this.searchInput.nativeElement.focus();
  }

  focusInput(): void {
    this.searchInput.nativeElement.focus();
  }
}

#searchInput on the HTML element creates a template reference variable named searchInput. @ViewChild('searchInput') with the string name retrieves that element. nativeElement gives you the actual underlying DOM element, so you can call any native DOM methods on it like .focus(), .click(), .scrollIntoView() etc.


Chapter 6 — Content Projection with ng-content


6.1 — What is Content Projection?

Imagine you want to build a reusable Card component — a styled container with a shadow, rounded corners, and padding. You want to use this card in multiple places, but each time with completely different content inside it. One card might have a user profile. Another might have a product listing. Another might have a chart.

If you use @Input(), you would have to pass all that content as strings or data — very limiting and messy.

Content projection is the solution. It lets a parent component pass HTML content directly into a child component. The child component decides where that content appears using <ng-content>.

Think of <ng-content> as a slot — a hole in the component's template where whatever you put between the component's opening and closing tags will appear.


6.2 — Basic Content Projection

src/app/card/card.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-card',
  imports: [],
  templateUrl: './card.html',
  styleUrl: './card.css'
})
export class Card { }

src/app/card/card.html:

<div class="card">
  <ng-content></ng-content>
</div>

src/app/card/card.css:

.card {
  background: white;
  border-radius: 12px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.1);
  padding: 24px;
  margin: 16px;
}

Now in any parent component, you can put any content between <app-card> and </app-card>:

import { Component } from '@angular/core';
import { Card } from '../card/card';

@Component({
  selector: 'app-home',
  imports: [Card],
  template: `
    <app-card>
      <h2>User Profile</h2>
      <p>Name: Rahul Sharma</p>
      <p>Role: Developer</p>
    </app-card>

    <app-card>
      <h2>Latest Stats</h2>
      <p>Projects: 20</p>
      <p>Clients: 15</p>
    </app-card>

    <app-card>
      <img src="/profile.jpg" alt="Photo">
      <p>This card has an image!</p>
    </app-card>
  `
})
export class Home { }

Each <app-card> is the same component, but each one displays completely different content. The <ng-content> in the card's template acts as a placeholder for whatever you put between the tags.


6.3 — Multiple Content Slots with select

Sometimes you want more than one slot in your component. For example, a card with a separate header, body, and footer. You can use the select attribute on <ng-content> to create named slots.

src/app/panel/panel.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-panel',
  imports: [],
  templateUrl: './panel.html',
  styleUrl: './panel.css'
})
export class Panel { }

src/app/panel/panel.html:

<div class="panel">
  <div class="panel-header">
    <ng-content select="[slot=header]"></ng-content>
  </div>
  <div class="panel-body">
    <ng-content select="[slot=body]"></ng-content>
  </div>
  <div class="panel-footer">
    <ng-content select="[slot=footer]"></ng-content>
  </div>
</div>

src/app/panel/panel.css:

.panel {
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  overflow: hidden;
}

.panel-header {
  background: #0a192f;
  color: white;
  padding: 16px 20px;
  font-weight: 600;
}

.panel-body {
  padding: 20px;
}

.panel-footer {
  background: #f5f5f5;
  padding: 12px 20px;
  border-top: 1px solid #e0e0e0;
}

Now in the parent, you assign content to specific slots using the slot attribute:

<app-panel>
  <div slot="header">User Settings</div>

  <div slot="body">
    <p>Email: rahul@example.com</p>
    <p>Theme: Dark</p>
    <p>Notifications: On</p>
  </div>

  <div slot="footer">
    <button>Save Changes</button>
  </div>
</app-panel>

select="[slot=header]" means: "project content that has the attribute slot="header"." The content goes into exactly the right slot.


Chapter 7 — ViewEncapsulation — How Component Styles Work


7.1 — The Problem with CSS

In regular HTML, CSS is global. If you write .title { color: red } in one stylesheet, every element with class title anywhere on the page turns red. This causes endless conflicts in large applications.

Angular solves this with ViewEncapsulation. By default, Angular scopes the CSS of each component so it only affects that component's own template. You can write .title { color: red } in navbar.css and .title { color: blue } in hero.css and they will not conflict at all.


7.2 — How Angular Does It

Angular achieves style scoping by automatically adding a unique attribute to every element in your component's template. For example, it might add _ngcontent-abc-c123 to every element in the Navbar component. Then it rewrites your CSS selectors to only match elements with that specific attribute.

So .title { color: red } in navbar.css becomes something like .title[_ngcontent-abc-c123] { color: red } internally. Other components have different attributes, so their elements are not affected.

You do not need to do anything for this to work — it is the default behavior.


7.3 — The Three ViewEncapsulation Modes

You can control this behavior using the encapsulation option in @Component:

import { Component, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-example',
  imports: [],
  template: `<p class="title">Hello</p>`,
  styles: [`.title { color: red }`],
  encapsulation: ViewEncapsulation.Emulated  // this is the default
})
export class Example { }

ViewEncapsulation.Emulated — the default. Angular emulates shadow DOM by adding unique attributes. Styles are scoped to the component. This is what you will use 99% of the time.

ViewEncapsulation.None — no scoping at all. The component's CSS becomes global and affects the entire application. Use this very carefully, only when you specifically want global styles from a component.

ViewEncapsulation.ShadowDom — uses the browser's real Shadow DOM for isolation. The strongest isolation possible. Styles are completely invisible to the outside world and vice versa. Has some browser compatibility considerations.


7.4 — The :host Selector

Inside a component's CSS file, :host refers to the component's own root element — the element matched by the selector.

/* navbar.css */

:host {
  /* Styles the <app-navbar> element itself */
  display: block;
  width: 100%;
}

:host(.active) {
  /* Styles <app-navbar class="active"> */
  border-bottom: 2px solid #64ffda;
}

This is useful when you want to style the wrapper element of your component without needing to add an extra wrapper div inside the template.


Chapter 8 — Change Detection — How Angular Knows What to Update


8.1 — What is Change Detection?

When data in your component changes — a property gets a new value, an array gets a new item, a user types something — Angular needs to update the DOM to reflect that change.

But how does Angular know something changed? It cannot watch every variable in your entire application at all times. That would be incredibly slow.

Angular uses a system called change detection to figure out what changed and what needs to be re-rendered.


8.2 — The Default Change Detection Strategy

By default, Angular uses ChangeDetectionStrategy.Default. This strategy works like this:

Whenever anything happens in your application — a button click, an HTTP response, a timer, any browser event — Angular runs a change detection cycle. During this cycle, it goes through the entire component tree from top to bottom and checks every component. For each component, it compares the current values of all template bindings with the previous values. If anything is different, it updates that part of the DOM.

This approach is simple and it always works correctly. But on very large applications with hundreds of components, checking everything on every event can become slow.


8.3 — OnPush Change Detection Strategy

Angular gives you a more performant option called ChangeDetectionStrategy.OnPush.

With OnPush, Angular only runs change detection on a component when one of these things happens:

An @Input() property receives a new reference (not just a mutation of an existing object). An event was triggered from within the component or its children. An Observable subscribed with the async pipe emits a new value. You manually trigger it.

import { Component, Input, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-product-card',
  imports: [],
  templateUrl: './product-card.html',
  styleUrl: './product-card.css',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductCard {
  @Input() productName: string = '';
  @Input() price: number = 0;
}

This means Angular skips this component during most change detection cycles, only checking it when something directly relevant to it changes. In large lists with hundreds of items, this can make a significant performance difference.

The key thing to understand with OnPush: if you mutate an object or array that is passed as input — like pushing a new item into an array that already exists — Angular will NOT detect the change. You need to replace the reference entirely.

// This will NOT trigger change detection with OnPush:
this.products.push(newProduct);  // mutating the existing array

// This WILL trigger change detection with OnPush:
this.products = [...this.products, newProduct];  // creating a new array

8.4 — Manually Triggering Change Detection

Sometimes with OnPush you update something and need to tell Angular to check your component right now. You do this using ChangeDetectorRef:

import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'app-notification',
  imports: [],
  template: `<p>{{ message }}</p>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class Notification {

  message: string = '';

  constructor(private cdr: ChangeDetectorRef) {}

  updateMessage(newMessage: string): void {
    this.message = newMessage;
    this.cdr.markForCheck(); // Tell Angular: please check this component
  }
}

this.cdr.markForCheck() schedules the component and its ancestors for checking in the next change detection cycle. Use this when you have OnPush and you update something that Angular cannot automatically detect.


Chapter 9 — Putting It All Together — A Real Component System


Let's build a complete, real-world example that uses everything from this phase together. We will build a Student Dashboard with a list of student cards where each card can be selected and the parent component reacts.

Step 1 — Create the project and components

ng new student-dashboard --style=css
cd student-dashboard
ng g c student-card --skip-tests
ng g c student-list --skip-tests

Step 2 — The StudentCard Component

This component receives student data via @Input() and emits an event via @Output() when clicked.

src/app/student-card/student-card.ts:

import { Component, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-student-card',
  imports: [],
  templateUrl: './student-card.html',
  styleUrl: './student-card.css'
})
export class StudentCard implements OnInit, OnDestroy {

  @Input({ required: true }) studentName!: string;
  @Input({ required: true }) grade!: string;
  @Input({ required: true }) score!: number;
  @Input() isSelected: boolean = false;

  @Output() selected = new EventEmitter<string>();

  status: string = '';

  ngOnInit(): void {
    this.status = this.score >= 60 ? 'Passing' : 'Needs Improvement';
    console.log(`${this.studentName} card initialized`);
  }

  ngOnDestroy(): void {
    console.log(`${this.studentName} card destroyed`);
  }

  onCardClick(): void {
    this.selected.emit(this.studentName);
  }
}

src/app/student-card/student-card.html:

<div class="card" [class.selected]="isSelected" (click)="onCardClick()">
  <div class="avatar">{{ studentName.charAt(0) }}</div>
  <div class="info">
    <h3>{{ studentName }}</h3>
    <p class="grade">Grade: {{ grade }}</p>
    <p class="score">Score: {{ score }}/100</p>
    <span class="status" [class.passing]="score >= 60" [class.failing]="score < 60">
      {{ status }}
    </span>
  </div>
</div>

src/app/student-card/student-card.css:

.card {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 20px;
  background: white;
  border-radius: 12px;
  border: 2px solid transparent;
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
  cursor: pointer;
  transition: all 0.2s;
}

.card:hover {
  border-color: #0070f3;
  transform: translateY(-2px);
}

.card.selected {
  border-color: #0070f3;
  background: #f0f7ff;
}

.avatar {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: #0070f3;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  font-weight: 700;
  flex-shrink: 0;
}

.info h3 {
  font-size: 16px;
  font-weight: 600;
  color: #1a1a2e;
  margin-bottom: 4px;
}

.grade, .score {
  font-size: 13px;
  color: #666;
  margin: 2px 0;
}

.status {
  display: inline-block;
  margin-top: 6px;
  padding: 3px 10px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
}

.passing {
  background: #d4edda;
  color: #155724;
}

.failing {
  background: #f8d7da;
  color: #721c24;
}

Step 3 — The StudentList Component

This component holds the list of students, renders student cards, and listens for selection events.

src/app/student-list/student-list.ts:

import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { StudentCard } from '../student-card/student-card';

interface Student {
  name: string;
  grade: string;
  score: number;
}

@Component({
  selector: 'app-student-list',
  imports: [StudentCard],
  templateUrl: './student-list.html',
  styleUrl: './student-list.css'
})
export class StudentList implements AfterViewInit {

  students: Student[] = [
    { name: 'Rahul Sharma', grade: 'A', score: 92 },
    { name: 'Priya Patel', grade: 'B', score: 78 },
    { name: 'Amit Kumar', grade: 'C', score: 55 },
    { name: 'Sneha Gupta', grade: 'A', score: 95 },
    { name: 'Vikram Singh', grade: 'B', score: 71 },
    { name: 'Neha Joshi', grade: 'D', score: 48 }
  ];

  selectedStudentName: string = '';

  @ViewChild(StudentCard) firstCard!: StudentCard;

  ngAfterViewInit(): void {
    console.log('First student card component:', this.firstCard);
    console.log('First student name:', this.firstCard.studentName);
  }

  onStudentSelected(studentName: string): void {
    this.selectedStudentName = studentName;
  }

  getSelectedStudent(): Student | undefined {
    return this.students.find(s => s.name === this.selectedStudentName);
  }
}

src/app/student-list/student-list.html:

<div class="dashboard">
  <h1>Student Dashboard</h1>

  @if (selectedStudentName) {
    <div class="selection-banner">
      ✓ Selected: <strong>{{ selectedStudentName }}</strong>
      — Score: {{ getSelectedStudent()?.score }}/100
    </div>
  }

  <div class="student-grid">
    @for (student of students; track student.name) {
      <app-student-card
        [studentName]="student.name"
        [grade]="student.grade"
        [score]="student.score"
        [isSelected]="selectedStudentName === student.name"
        (selected)="onStudentSelected($event)">
      </app-student-card>
    }
  </div>
</div>

src/app/student-list/student-list.css:

.dashboard {
  max-width: 900px;
  margin: 0 auto;
  padding: 40px 24px;
}

h1 {
  font-size: 32px;
  color: #1a1a2e;
  margin-bottom: 24px;
}

.selection-banner {
  background: #f0f7ff;
  border: 1px solid #0070f3;
  border-radius: 8px;
  padding: 12px 20px;
  margin-bottom: 24px;
  color: #0070f3;
  font-size: 15px;
}

.student-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 16px;
}

Step 4 — Wire it in app.ts

src/app/app.ts:

import { Component } from '@angular/core';
import { StudentList } from './student-list/student-list';

@Component({
  selector: 'app-root',
  imports: [StudentList],
  template: `<app-student-list></app-student-list>`,
  styles: [`
    :host {
      display: block;
      background: #f5f7fa;
      min-height: 100vh;
    }
  `]
})
export class App { }

src/styles.css:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

Run ng serve -o. You will see a complete Student Dashboard where:

  • Each student card is initialized with ngOnInit which calculates the status
  • Clicking a card emits an event via @Output() with the student's name
  • The parent StudentList receives the event and updates selectedStudentName
  • The selected card gets highlighted because [isSelected]="selectedStudentName === student.name" becomes true
  • The selection banner appears via @if showing the selected student's details
  • @ViewChild gives the parent direct access to the first StudentCard component

Every concept from this phase is at work in this example.


Phase 3 — Complete Summary

This phase went deep into how components really work. Here is everything you covered:

Lifecycle hooks — Angular calls special methods at specific moments of a component's life. ngOnInit runs once when the component is ready — use it to load data. ngOnDestroy runs just before the component is removed — use it to clean up timers and subscriptions. ngOnChanges runs whenever an @Input() value changes. ngAfterViewInit runs once after the template is fully rendered — use it when you need @ViewChild references.

@Input() — Passes data from a parent component down to a child component. You use @Input() to declare which properties the child accepts. The parent passes values using square bracket binding [propertyName]="value". You can mark inputs as required and give them aliases.

@Output() and EventEmitter — Sends events from a child component up to a parent. The child declares an EventEmitter with @Output() and calls .emit() to fire the event with optional data. The parent listens using event binding (eventName)="handler($event)".

@ViewChild — Gives a parent component direct access to a child component instance or a DOM element. You use it after ngAfterViewInit because the view must be fully rendered first. With a component type you get the component instance and can call its methods. With a template reference variable #name you get an ElementRef to access the raw DOM element.

Content projection with ng-content — Lets you build wrapper components that accept HTML content from outside. <ng-content> is the slot where projected content appears. Multiple named slots are created using select="[slot=name]" for finer control over where different pieces of content go.

ViewEncapsulation — Angular scopes component CSS by default so styles never leak out. Emulated is the default and works for almost everything. None makes styles global. ShadowDom uses real browser shadow DOM for the strongest isolation. :host selects the component's own root element in CSS.

Change detection — Angular checks the component tree for changes after every browser event. Default strategy checks everything on every cycle — simple and always correct. OnPush strategy only checks when inputs change or events fire from within — more performant for large lists and complex UIs. With OnPush you must replace object and array references rather than mutating them. ChangeDetectorRef.markForCheck() manually triggers a check when needed.


What's Next — Phase 4

In Phase 4 we cover Data Binding and Directives — the complete syntax for making your templates dynamic:

The four types of data binding in complete depth — interpolation, property binding, event binding, and two-way binding. You will understand exactly when to use each one.

Angular's built-in control flow — @if for conditionals, @for for loops, @switch for multiple conditions. This is how you make your templates respond to data.

Built-in directives — NgClass, NgStyle, and how to apply dynamic styles.

Building your own custom directives from scratch.

The ng-template and ng-container elements and what they are for.

Phase 2 — Angular Fundamentals

Chapter 1 — What is Angular and How Does it Think?


1.1 — What is Angular?

Angular is a framework for building web applications. A framework is basically a set of tools, rules, and pre-written code that helps you build things faster and in a more organized way.

Think of it like this. If you wanted to build a house, you could gather all the raw materials yourself — wood, bricks, cement, tools — and figure everything out from scratch. That would take forever. OR you could use a construction kit that already has everything organized, pre-measured, and ready to assemble. Angular is that construction kit for web apps.

Without Angular, building a complex web app in plain HTML, CSS, and JavaScript means you have to manually manage everything — updating the page when data changes, organizing your code, handling navigation between pages. It gets messy very fast.

Angular handles all of that for you. You focus on building features. Angular handles the rest.


1.2 — The Problem Angular Solves — Traditional Websites vs Angular

To truly understand why Angular exists, you need to understand the problem it solves.

On a traditional website, every time you click a link, here is what happens:

Your browser sends a request to the server saying "hey, I want the About page." The server prepares a brand new HTML file for the About page and sends it back. Your browser completely throws away the current page and renders the new one from scratch. You see a white flash. Everything resets. Your scroll position is gone. Any data you had on the previous page is gone.

Now multiply this by every single click. Every navigation. Every page visit. This is slow and feels clunky.

Angular solves this completely with a concept called a Single Page Application, or SPA.

Here is how it works with Angular:

When you first open an Angular app, the browser loads one single HTML file — just once. After that, Angular completely takes over. When you click a link, Angular does NOT ask the server for a new page. Instead, it dynamically swaps out pieces of the screen — showing some components, hiding others — right there in the browser. The URL in your address bar changes, the content on screen changes, but the page never actually reloads.

This is why Angular apps feel fast and smooth. There is no round trip to the server. No white flash. No state reset. Everything happens instantly in the browser.


1.3 — The Most Important Concept — Think in Components

This is the single most important mental shift you need to make when learning Angular. Stop thinking about pages. Start thinking about components.

A component is a self-contained, reusable piece of your UI. It has its own HTML structure, its own CSS styles, and its own TypeScript logic. It is like a LEGO block for your web app.

Look at any website and you can immediately identify the components:

┌──────────────────────────────────────┐
│           NavbarComponent            │  ← handles logo, menu links
├──────────────────────────────────────┤
│                                      │
│           HeroComponent              │  ← handles headline, CTA button
│                                      │
├──────────────────────────────────────┤
│                                      │
│          SkillsComponent             │  ← handles section title
│  ┌──────────┐  ┌──────────┐          │
│  │ SkillCard│  │ SkillCard│  ...     │  ← each card is its own component
│  └──────────┘  └──────────┘          │
│                                      │
├──────────────────────────────────────┤
│           FooterComponent            │  ← handles footer text
└──────────────────────────────────────┘

Every section is a component. And components can live inside other components — the Skills section is a component, and inside it, each individual skill card is also a component.

This nesting of components inside components is called a component tree. At the very top of this tree sits one root component. In Angular that root component is called App. Every other component in your entire application lives somewhere inside it.

Why is this approach powerful? Because each component is independent. You can reuse the same SkillCard component ten times on the page. Each instance manages itself. If you update the SkillCard component, every instance automatically gets the update. You build once, use everywhere.


1.4 — How Angular Components Work — The Big Picture

Every Angular component has exactly three parts working together:

The TypeScript file is the brain. This is where your data and logic live. What is the user's name? What items are in the list? What should happen when a button is clicked? All of that lives in the TypeScript class.

The HTML file is the face. This is the template — what the component actually looks like on screen. It uses the data from the TypeScript file to display things dynamically.

The CSS file is the clothing. Styles that only apply to this component and nothing else in the app.

These three files together make one complete component. They always work as a team.


Chapter 2 — Setting Up Angular


2.1 — Installing Node.js

Before you can use Angular, you need Node.js installed on your computer.

Node.js is a JavaScript runtime — it lets JavaScript run outside the browser, directly on your computer. Angular's tools need this to work.

Go to nodejs.org and download the LTS version. LTS stands for Long Term Support. It is the stable, tested, recommended version. Do not download the "Current" version — that one may have experimental features that are unstable.

After the installation finishes, open your terminal (on Windows: search for "Command Prompt" or "PowerShell") and run:

node --version

You should see a version number printed like v22.17.1. Then run:

npm --version

You should see something like 11.12.1.

npm stands for Node Package Manager. It came bundled automatically with Node.js. It is the tool you use to install Angular and every other JavaScript library throughout this course. Think of npm like an app store for JavaScript packages.

If both commands print version numbers, your Node.js installation is working perfectly.


2.2 — Installing the Angular CLI

CLI stands for Command Line Interface. The Angular CLI is a tool you install once and use throughout your entire Angular career.

What does the CLI do? It handles the boring, repetitive work for you. Instead of manually creating files, writing boilerplate code, and configuring everything yourself — you type one short command and the CLI does it all. Creating a new project, generating a component, running a development server, building for production — all of this is one command away.

Install it by running this in your terminal:

npm install -g @angular/cli

The -g flag means global. This installs the CLI on your entire computer so you can use it in any project, anywhere, anytime.

After installation completes, verify it worked:

ng version

This will print out Angular CLI version information. ng is the command you type for everything Angular related. You will type ng hundreds of times throughout this course.


2.3 — Creating Your First Angular Project

Navigate to wherever you keep your coding projects in the terminal. For example:

cd Desktop

Now create a new Angular project:

ng new my-angular-app

The CLI will ask you two questions interactively.

First question — Which stylesheet format would you like to use?

You will see options like CSS, SCSS, SASS, LESS. Use your arrow keys to highlight CSS and press Enter. CSS is the standard and it is what we use throughout this course.

Second question — Do you want to enable Server-Side Rendering (SSR)?

Type N and press Enter. Server-Side Rendering is an advanced topic we do not need right now.

After you answer both questions, the CLI gets to work. It creates the project folder, generates all the necessary files, and automatically runs npm install to download all the packages Angular needs. This step takes a minute or two because it is downloading packages from the internet.

Once it finishes, navigate into your new project:

cd my-angular-app

Now start the development server:

ng serve

Open your browser and go to:

http://localhost:4200

You will see the Angular default welcome page. Your app is running.

Here is something really useful about ng serve — it watches your files. Every single time you save any change to any file in your project, it automatically recompiles the code and refreshes the browser. You never need to manually refresh while developing. Just save and see changes instantly.

To stop the server at any time, press Ctrl + C in the terminal.


Chapter 3 — Understanding Your Project Files


Open your project folder in VS Code. You will see this structure:

my-angular-app/
│
├── public/                   ← your images, fonts, icons go here
│
├── src/                      ← all your app code lives here
│   ├── app/
│   │   ├── app.ts            ← root component logic
│   │   ├── app.html          ← root component template
│   │   ├── app.css           ← root component styles
│   │   ├── app.spec.ts       ← root component tests
│   │   ├── app.config.ts     ← app-wide configuration
│   │   └── app.routes.ts     ← all route definitions
│   │
│   ├── index.html            ← the ONE html file
│   ├── main.ts               ← entry point, app starts here
│   └── styles.css            ← global styles
│
├── angular.json              ← Angular project configuration
├── package.json              ← project dependencies
├── tsconfig.json             ← base TypeScript config
├── tsconfig.app.json         ← TypeScript config for app
└── tsconfig.spec.json        ← TypeScript config for tests

Two things to notice immediately.

The public/ folder is outside src/. It sits at the root level of your project. Any images, fonts, or icons you want to use in your app go inside this folder.

There is no app.module.ts file anywhere. Modern Angular does not use module files. Everything works without them.

Now let's go through every important file one by one and understand exactly what each one does and why it exists.


3.1 — index.html — The One HTML File

Open src/index.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>MyAngularApp</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

The first thing you notice is that this is a completely normal HTML file — except for one tag: <app-root></app-root>.

That tag is not a standard HTML element. Your browser has no idea what <app-root> means. But Angular does. <app-root> is the selector for your root App component. When Angular starts, it finds this tag and replaces it with your entire application.

Everything your user ever sees — every component, every page, every button — gets rendered inside that one <app-root> tag.

This file stays exactly as it is throughout your entire project. You will almost never touch it.

The <base href="/"> line is required for Angular's router to work correctly. It tells the browser that all URLs in this app start from the root domain. Without this line, navigation would break.


3.2 — main.ts — Where Your App Starts

Open src/main.ts:

import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';

bootstrapApplication(App, appConfig)
  .catch((err) => console.error(err));

This is the entry point of your application. It is the very first TypeScript file that runs when someone opens your app.

bootstrapApplication is a function from Angular that starts your application. You pass it two things — the root component (App) and the configuration object (appConfig).

When this function runs, Angular boots up, reads the configuration, finds the App component, matches its selector app-root to the <app-root> tag in index.html, and renders your entire app inside it.

The .catch() at the end is just basic error handling. If something goes wrong during startup, it logs the error to the browser console.

You will rarely edit this file. Its job is simple — start the app.


3.3 — app.config.ts — The App's Configuration Center

Open src/app/app.config.ts:

import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes)
  ]
};

This file is passed to bootstrapApplication in main.ts. It tells Angular how to configure the application at the global level.

The providers array is the key part. Whatever you put in this array becomes available throughout your entire application.

provideRouter(routes) sets up Angular's routing system using the routes you will define in app.routes.ts. Without this line, navigation between pages would not work.

provideZoneChangeDetection({ eventCoalescing: true }) is a performance setting. It tells Angular to batch multiple browser events together before checking what needs to update on screen. This reduces unnecessary work. You can leave this as it is — it is already the correct setting.

As you progress through this course, you will add more things to this providers array. For example, when you start making API calls in Phase 8, you will add provideHttpClient() here. When you add animations in Phase 10, you will add provideAnimations(). Everything that needs to work app-wide goes here.


3.4 — app.routes.ts — Your App's Navigation Map

Open src/app/app.routes.ts:

import { Routes } from '@angular/router';

export const routes: Routes = [];

This file is a simple TypeScript file that exports an array of route objects. Each route object maps a URL path to a component. Right now the array is empty because we have not created any pages yet.

When we reach Phase 6, this file will look something like this:

export const routes: Routes = [
  { path: '', component: Home },
  { path: 'about', component: About },
  { path: 'projects', component: Projects }
];

This means: when the URL is /, show the Home component. When the URL is /about, show the About component. And so on.

For now just know this file exists and this is where all your app's navigation is defined.


3.5 — app.ts — The Root Component

Open src/app/app.ts. This is the most important file to understand right now:

import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet],
  templateUrl: './app.html',
  styleUrl: './app.css'
})
export class App {
  protected readonly title = signal('my-angular-app');
}

Let's go through every single part carefully because you will write code like this constantly.


The import statement at the top

import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';

Just like in regular TypeScript, before you can use something, you import it. Component is the decorator that tells Angular this class is a component. signal is Angular's reactive value system — more on this in a moment. RouterOutlet is imported because the template uses <router-outlet>.


The @Component decorator

@Component({
  selector: 'app-root',
  imports: [RouterOutlet],
  templateUrl: './app.html',
  styleUrl: './app.css'
})

A decorator is a special piece of code that starts with @ and sits right above a class. It adds information and behavior to that class without changing the class itself.

The @Component decorator is what tells Angular "hey, this TypeScript class is not just any class — it is a component." Without this decorator, Angular would completely ignore the class.

Inside the decorator, you pass a configuration object:

selector: 'app-root' — this is the custom HTML tag name for this component. Anywhere you write <app-root> in an HTML file, Angular will render this component there. Every component selector starts with app- by convention.

imports: [RouterOutlet] — this is the list of everything your template uses. RouterOutlet is here because app.html contains <router-outlet>. This is the most important concept in Angular — if you use something in the template, you must import it here. We will talk about this deeply in the next chapter.

templateUrl: './app.html' — the path to this component's HTML template file. Angular reads this file and uses it as the component's visual structure.

styleUrl: './app.css' — the path to this component's CSS file. The important thing to understand is that these styles are scoped — they only affect this component, not any other component in the app. You can use the same class name like .title in ten different components and they will never conflict with each other.


The class

export class App {
  protected readonly title = signal('my-angular-app');
}

This is a regular TypeScript class. The class name is App — that is the name Angular uses to identify this component in TypeScript code.

The title property is interesting. It is a signal — Angular's modern way to hold a reactive value.


Understanding Signals

A signal is a container that holds a value and notifies Angular automatically when that value changes.

When you use a regular TypeScript property, Angular has to constantly check all properties to see if anything changed. With a signal, Angular knows exactly which value changed and only updates the specific parts of the screen that depend on that value. This makes your app more efficient.

You create a signal by calling the signal() function with an initial value:

title = signal('my-angular-app');
count = signal(0);
isLoggedIn = signal(false);
userName = signal('Rahul');

To read a signal's value inside a template, you call it like a function — with parentheses ():

<h1>{{ title() }}</h1>
<p>Count is: {{ count() }}</p>

To update a signal's value in TypeScript, you use .set():

this.title.set('Welcome!');
this.count.set(10);
this.isLoggedIn.set(true);

To update a signal based on its current value, you use .update():

this.count.update(currentValue => currentValue + 1);
// If count was 5, it is now 6

That is all you need to know about signals for now. We go deep on them in Phase 10. For now just remember: when you use a signal in a template, add () after its name.


3.6 — app.html — The Root Template

Open src/app/app.html. It contains a large default welcome page. Delete all of it and replace with this:

<h1>Hello Angular!</h1>
<p>{{ title() }}</p>
<router-outlet></router-outlet>

Notice {{ title() }} — the double curly braces {{ }} is called interpolation. It is how you display a TypeScript value in your HTML. Since title is a signal, you add () to read its value.

<router-outlet> is a placeholder. When you set up routing in Phase 6, page components will appear in this exact spot when the user navigates. Think of it as a blank TV screen — routes will "play" on this screen.

Save the file and check localhost:4200. Your browser instantly refreshes and shows your new content.


3.7 — styles.css — Global Styles

Open src/styles.css. This is the one place where your styles affect the entire application. Every component will be affected by what you put here.

Use this file for:

  • Global CSS resets
  • Font family settings on the body
  • Global color variables
  • Any style that should apply everywhere
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background-color: #f5f5f5;
  color: #333;
}

Every component has its own CSS file for styles that are specific to that component only. styles.css is the only one that is truly global.


3.8 — public/ Folder — Static Files

The public/ folder lives outside src/ at the root of your project. This is where you put static files that your app needs — images, custom fonts, icons, and any other files that don't change.

If you have a profile photo for your portfolio, you put it inside public/. Let's say you create a folder inside public/ called images and put photo.jpg inside it:

public/
└── images/
    └── photo.jpg

To use it in any template:

<img src="/images/photo.jpg" alt="My photo">

You reference files from public/ with a leading /. Angular serves everything inside public/ directly at the root of your app's URL.


3.9 — angular.json — Project Configuration

This is Angular's master configuration file. It is automatically generated and you rarely need to edit it manually. But understanding what it does is useful.

It tells Angular things like: where your source files are, where to put the production build output, which global stylesheets to load, which assets to include.

The one time you will manually edit it as a beginner is when you want to add a global CSS library like Bootstrap. You would add the Bootstrap CSS file path to the styles array:

"styles": [
  "src/styles.css",
  "node_modules/bootstrap/dist/css/bootstrap.min.css"
]

Other than that, leave this file as it is.


3.10 — package.json — Your Project's Dependency List

{
  "scripts": {
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test"
  },
  "dependencies": {
    "@angular/core": "^21.2.0",
    "@angular/common": "^21.2.0",
    "@angular/router": "^21.2.0"
  },
  "devDependencies": {
    "@angular/cli": "^21.2.5",
    "typescript": "~5.9.2",
    "vitest": "^4.0.8"
  }
}

dependencies — packages your app needs to actually run. These get included in your production build.

devDependencies — packages only needed during development, like the CLI and the test runner (Vitest). These are NOT included in the production build. Users downloading your app don't need these.

scripts — shortcuts for common tasks. When you type npm start in the terminal, it runs ng serve. When you type npm run build, it runs ng build. These are just convenience shortcuts.


3.11 — tsconfig Files

You have three TypeScript configuration files:

tsconfig.json is the base configuration that the other two extend. It contains settings shared by everything.

tsconfig.app.json is the TypeScript configuration specifically for building your application.

tsconfig.spec.json is the TypeScript configuration specifically for running your tests.

The most important setting in tsconfig.json is "strict": true. This enables strict TypeScript type checking. It catches more bugs before your code even runs. Always keep this set to true. If TypeScript gives you an error, fix the code — do not disable strict mode to silence the error.


Chapter 4 — Components in Depth


4.1 — Generating a Component

The Angular CLI generates components for you with a single command:

ng generate component navbar

The shorthand for the same command:

ng g c navbar

When you run this inside your Angular project, the CLI creates a new folder called navbar inside src/app/ and generates these files:

src/app/navbar/
├── navbar.ts
├── navbar.html
├── navbar.css
└── navbar.spec.ts

The naming in Angular is clean and simple. The files are just navbar.ts, navbar.html, and navbar.css. No extra words in the file names. The .spec.ts file is for writing tests — we cover tests in Phase 10.


4.2 — What the Generated Component Looks Like

Open the generated navbar.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-navbar',
  imports: [],
  templateUrl: './navbar.html',
  styleUrl: './navbar.css'
})
export class Navbar {

}

This is clean and minimal. Let's look at what each part means.

The selector is 'app-navbar'. This is the custom HTML tag you will use to place this component anywhere in your app. Want the navbar to appear somewhere? Write <app-navbar></app-navbar> in any template.

The imports array is empty. You add things here as your template needs them. We will talk about what to add here very shortly.

The class name is Navbar. Not NavbarComponent — just Navbar. Angular uses this class name when you import the component in TypeScript.

The template file is navbar.html and the style file is navbar.css. Angular automatically points to these files correctly.


4.3 — Adding Data to a Component

A component's TypeScript class is where all your data and logic lives. Let's add some real content to the Navbar component:

navbar.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-navbar',
  imports: [],
  templateUrl: './navbar.html',
  styleUrl: './navbar.css'
})
export class Navbar {
  siteName: string = 'MyPortfolio';
  navLinks: string[] = ['Home', 'About', 'Projects', 'Contact'];
}

You just added two properties to the class. siteName is a string that holds the website name. navLinks is an array of strings holding the navigation link labels.

Now let's display this data in the template.


4.4 — Displaying Data in the Template

Open navbar.html and replace the placeholder content:

<nav>
  <div class="logo">{{ siteName }}</div>
  <ul>
    @for (link of navLinks; track link) {
      <li><a href="#">{{ link }}</a></li>
    }
  </ul>
</nav>

Two important things happening here.

{{ siteName }} — the double curly braces read the siteName property from the TypeScript class and display its value in the HTML. This is called interpolation. Whatever value siteName holds in TypeScript will appear here on screen.

@for (link of navLinks; track link) — this is Angular's built-in loop syntax. It loops through every item in the navLinks array and renders the HTML inside the block once for each item. Since navLinks has 4 items, Angular renders 4 <li> elements automatically.

The track link part tells Angular to use the link value itself to uniquely identify each item. This helps Angular update the list efficiently when items change.

We cover @for, @if, and all the template syntax deeply in Phase 4. For now just know that @for loops and {{ }} displays data.


4.5 — Using One Component Inside Another

This is the core workflow in Angular. You build small components and then compose them together inside larger ones.

Let's say you want to use Navbar inside your root App component. Two steps.

Step 1 — Import the component in app.ts:

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { Navbar } from './navbar/navbar';

@Component({
  selector: 'app-root',
  imports: [
    RouterOutlet,
    Navbar
  ],
  templateUrl: './app.html',
  styleUrl: './app.css'
})
export class App { }

You imported Navbar from its file path './navbar/navbar' — that is the folder name followed by the file name, without the .ts extension. Then you added it to the imports array inside @Component.

Step 2 — Use its selector as a tag in app.html:

<app-navbar></app-navbar>
<router-outlet></router-outlet>

Save both files and check localhost:4200. Your Navbar component is now rendered at the top of the page.

This is the complete workflow:

  1. Generate a component with ng g c
  2. Import it in the parent's imports array
  3. Use its selector as an HTML tag in the parent's template

4.6 — The Golden Rule of Angular

If you use something in your template, it must be in the component's imports array.

This is the most important rule to remember. Let's break it down with examples.

If your template uses another component:

// In the template: <app-navbar></app-navbar>
// In the imports array:
imports: [Navbar]

If your template uses Angular's @for loop with a pipe:

// In the template: {{ price | currency }}
// In the imports array:
imports: [CurrencyPipe]

If your template uses router navigation:

// In the template: <a routerLink="/about">About</a>
// In the imports array:
imports: [RouterLink]

If you forget to add something to imports, Angular will throw a clear error in the browser console telling you exactly what is missing. It will say something like 'Navbar' is not a known element. That error always means the same thing — go add it to the imports array.


Chapter 5 — How Angular Starts From Zero to Running App


Let's trace through exactly what happens the moment someone opens your Angular app in the browser. Understanding this sequence means when something goes wrong during startup, you know exactly where to look.

Step 1 — The browser requests the page

The user opens localhost:4200. The dev server receives this request and responds with index.html.

Step 2 — The browser reads index.html

The browser starts reading index.html. It sees <app-root></app-root> in the body. It has absolutely no idea what this tag means. It continues reading.

Step 3 — Angular's JavaScript loads

The Angular CLI automatically adds <script> tags to index.html during the build. The browser downloads these JavaScript files — they contain your entire application compiled into JavaScript.

Step 4 — main.ts runs

The JavaScript executes and main.ts runs first. This line executes:

bootstrapApplication(App, appConfig)

This is the ignition switch. Angular starts up.

Step 5 — appConfig is processed

Angular reads appConfig and processes all the providers. The router is initialized with your routes from app.routes.ts. Any other app-wide setup happens here.

Step 6 — The App component renders

Angular looks at the App component's selector: 'app-root'. It searches index.html for a matching element. It finds <app-root></app-root> and replaces it with the contents of app.html.

Step 7 — Child components render

Angular processes app.html. It finds <app-navbar>. Angular searches the App component's imports array, finds the Navbar component, and renders navbar.html in that spot. This happens for every child component in every template.

Step 8 — The app is live

The complete component tree is rendered on screen. Angular is now watching for user interactions — button clicks, form inputs, navigation — and will update the screen reactively as things change.

Here is the complete flow in one diagram:

User opens localhost:4200
          ↓
Server sends index.html
          ↓
Browser downloads Angular JS bundles
          ↓
main.ts runs
          ↓
bootstrapApplication(App, appConfig)
          ↓
appConfig processed — router set up
          ↓
App component's selector 'app-root'
matched in index.html
app.html rendered inside it
          ↓
Child components rendered
(each one matched from imports array)
          ↓
App is fully live ✓

Chapter 6 — Angular CLI Commands


The Angular CLI is your daily tool throughout this course. Here are all the commands you will use, explained clearly.


Creating a new project

ng new project-name

This creates a brand new Angular project from scratch. The CLI asks you about stylesheet format and SSR, then generates everything automatically.


Starting the development server

ng serve

This starts the live development server at localhost:4200. It watches your files and auto-refreshes on save. You keep this running while you develop.

ng serve -o

The -o flag stands for --open. It starts the server AND automatically opens your browser. Saves you one step.

ng serve --port 3000

Use a different port if 4200 is already occupied by something else.


Generating a component

ng generate component header
ng g c header

Both commands do the same thing. The second is just shorter. This creates a new folder with header.ts, header.html, header.css, and header.spec.ts.

ng g c header --skip-tests

Adds --skip-tests to skip generating the .spec.ts test file. We will use this while learning to keep things clean.

ng g c products/product-card

Generates a product-card component inside a products subfolder. This creates src/app/products/product-card/.

ng g c header --flat

Generates the component files without creating a new subfolder. The files go directly in whatever folder you are in.


Generating other things

ng g s user          ← generate a service
ng g p truncate      ← generate a pipe
ng g d highlight     ← generate a directive
ng g guard auth      ← generate a route guard

We will use all of these in later phases. Just know these commands exist.


Building for production

ng build

Compiles your app into optimized JavaScript files ready to deploy. The output goes into a dist/ folder. These files are what you upload to a web server.

ng build --configuration=production

The full production build with all optimizations — minification, tree-shaking (removing unused code), and everything else that makes your app as small and fast as possible.


Other useful commands

ng version

Shows you the exact versions of Angular, CLI, Node.js, and TypeScript installed. Useful for debugging compatibility issues.

ng test

Runs your test suite using Vitest.

ng lint

Checks your code for quality issues and style problems.


Chapter 7 — Building a Real Portfolio App


Now we put everything from this phase into practice. We will build a Developer Portfolio Page with four components — Navbar, Hero, Skills, and Footer. This is a real Angular project using everything you just learned.


Step 1 — Create the project

Open your terminal and run:

ng new portfolio-app --style=css

When asked about SSR, type N. Wait for the setup to complete, then:

cd portfolio-app

Step 2 — Generate the four components

ng g c navbar --skip-tests
ng g c hero --skip-tests
ng g c skills --skip-tests
ng g c footer --skip-tests

After running all four commands, your src/app/ folder looks like this:

src/app/
├── navbar/
│   ├── navbar.ts
│   ├── navbar.html
│   └── navbar.css
├── hero/
│   ├── hero.ts
│   ├── hero.html
│   └── hero.css
├── skills/
│   ├── skills.ts
│   ├── skills.html
│   └── skills.css
├── footer/
│   ├── footer.ts
│   ├── footer.html
│   └── footer.css
├── app.ts
├── app.html
├── app.css
├── app.config.ts
└── app.routes.ts

Step 3 — Build the Navbar Component

src/app/navbar/navbar.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-navbar',
  imports: [],
  templateUrl: './navbar.html',
  styleUrl: './navbar.css'
})
export class Navbar {
  siteName: string = 'DevPortfolio';
  navLinks: string[] = ['Home', 'About', 'Projects', 'Contact'];
}

src/app/navbar/navbar.html:

<nav>
  <div class="logo">{{ siteName }}</div>
  <ul>
    @for (link of navLinks; track link) {
      <li><a href="#">{{ link }}</a></li>
    }
  </ul>
</nav>

src/app/navbar/navbar.css:

nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 18px 48px;
  background: #0f0f23;
}

.logo {
  font-size: 22px;
  font-weight: 700;
  color: #64ffda;
  letter-spacing: 1px;
}

ul {
  list-style: none;
  display: flex;
  gap: 32px;
  margin: 0;
  padding: 0;
}

a {
  color: #ccd6f6;
  text-decoration: none;
  font-size: 15px;
  transition: color 0.2s;
}

a:hover {
  color: #64ffda;
}

Step 4 — Build the Hero Component

src/app/hero/hero.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-hero',
  imports: [],
  templateUrl: './hero.html',
  styleUrl: './hero.css'
})
export class Hero {
  greeting: string = "Hi, I'm";
  name: string = 'Rahul Sharma';
  role: string = 'Angular Developer';
  description: string = 'I build fast, scalable web apps with modern Angular and TypeScript.';
  yearsExp: number = 3;
  projectsBuilt: number = 20;
  happyClients: number = 15;
}

src/app/hero/hero.html:

<section class="hero">
  <p class="greeting">{{ greeting }}</p>
  <h1>{{ name }}</h1>
  <h2>{{ role }}</h2>
  <p class="description">{{ description }}</p>

  <div class="stats">
    <div class="stat">
      <span class="number">{{ yearsExp }}+</span>
      <span class="label">Years Experience</span>
    </div>
    <div class="stat">
      <span class="number">{{ projectsBuilt }}+</span>
      <span class="label">Projects Built</span>
    </div>
    <div class="stat">
      <span class="number">{{ happyClients }}+</span>
      <span class="label">Happy Clients</span>
    </div>
  </div>

  <button class="cta">View My Work</button>
</section>

src/app/hero/hero.css:

.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 90vh;
  text-align: center;
  padding: 40px 24px;
  background: #0a192f;
}

.greeting {
  font-size: 18px;
  color: #64ffda;
  margin-bottom: 8px;
}

h1 {
  font-size: 60px;
  font-weight: 700;
  color: #ccd6f6;
  margin-bottom: 8px;
}

h2 {
  font-size: 30px;
  font-weight: 400;
  color: #8892b0;
  margin-bottom: 20px;
}

.description {
  max-width: 560px;
  line-height: 1.8;
  color: #8892b0;
  font-size: 16px;
}

.stats {
  display: flex;
  gap: 56px;
  margin: 48px 0;
}

.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.number {
  font-size: 40px;
  font-weight: 700;
  color: #64ffda;
}

.label {
  font-size: 13px;
  color: #8892b0;
  text-transform: uppercase;
  letter-spacing: 1px;
}

.cta {
  padding: 14px 40px;
  background: transparent;
  color: #64ffda;
  border: 2px solid #64ffda;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
  letter-spacing: 1px;
  transition: background 0.2s;
}

.cta:hover {
  background: rgba(100, 255, 218, 0.1);
}

Step 5 — Build the Skills Component

src/app/skills/skills.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-skills',
  imports: [],
  templateUrl: './skills.html',
  styleUrl: './skills.css'
})
export class Skills {
  sectionTitle: string = 'Skills & Technologies';

  skills = [
    { name: 'Angular', level: 90 },
    { name: 'TypeScript', level: 88 },
    { name: 'RxJS', level: 78 },
    { name: 'HTML & CSS', level: 92 },
    { name: 'Node.js', level: 72 },
    { name: 'Git', level: 85 }
  ];
}

src/app/skills/skills.html:

<section class="skills">
  <h2>{{ sectionTitle }}</h2>

  <div class="grid">
    @for (skill of skills; track skill.name) {
      <div class="card">
        <div class="card-header">
          <span class="skill-name">{{ skill.name }}</span>
          <span class="skill-level">{{ skill.level }}%</span>
        </div>
        <div class="bar-track">
          <div class="bar-fill" [style.width.%]="skill.level"></div>
        </div>
      </div>
    }
  </div>
</section>

src/app/skills/skills.css:

.skills {
  padding: 80px 48px;
  background: #112240;
  text-align: center;
}

h2 {
  font-size: 38px;
  color: #ccd6f6;
  margin-bottom: 52px;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 24px;
  max-width: 860px;
  margin: 0 auto;
}

.card {
  background: #0a192f;
  padding: 22px 26px;
  border-radius: 8px;
  border: 1px solid #233554;
}

.card-header {
  display: flex;
  justify-content: space-between;
  margin-bottom: 12px;
}

.skill-name {
  color: #ccd6f6;
  font-size: 15px;
  font-weight: 600;
}

.skill-level {
  color: #64ffda;
  font-size: 14px;
}

.bar-track {
  height: 6px;
  background: #233554;
  border-radius: 3px;
  overflow: hidden;
}

.bar-fill {
  height: 100%;
  background: linear-gradient(90deg, #64ffda, #0070f3);
  border-radius: 3px;
}

Step 6 — Build the Footer Component

src/app/footer/footer.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-footer',
  imports: [],
  templateUrl: './footer.html',
  styleUrl: './footer.css'
})
export class Footer {
  year: number = new Date().getFullYear();
  name: string = 'Rahul Sharma';
}

src/app/footer/footer.html:

<footer>
  <p>Designed & Built by <span class="highlight">{{ name }}</span></p>
  <p class="copy">© {{ year }} — All rights reserved</p>
</footer>

src/app/footer/footer.css:

footer {
  text-align: center;
  padding: 36px;
  background: #0a192f;
  border-top: 1px solid #233554;
}

p {
  color: #8892b0;
  font-size: 14px;
  margin: 4px 0;
}

.highlight {
  color: #64ffda;
}

.copy {
  font-size: 12px;
  margin-top: 8px;
}

Step 7 — Connect All Four Components in app.ts

Now wire everything together. Open src/app/app.ts and replace its entire contents:

import { Component } from '@angular/core';
import { Navbar } from './navbar/navbar';
import { Hero } from './hero/hero';
import { Skills } from './skills/skills';
import { Footer } from './footer/footer';

@Component({
  selector: 'app-root',
  imports: [
    Navbar,
    Hero,
    Skills,
    Footer
  ],
  templateUrl: './app.html',
  styleUrl: './app.css'
})
export class App { }

Open src/app/app.html and replace everything with:

<app-navbar></app-navbar>
<app-hero></app-hero>
<app-skills></app-skills>
<app-footer></app-footer>

Open src/styles.css and replace everything with:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

Now run ng serve -o in the terminal. Your complete portfolio page will open in the browser.


Understanding What Just Happened

Let's trace through the complete flow of this project to make sure everything clicks.

When the browser opened localhost:4200, the server sent index.html. Angular loaded and ran main.ts, which called bootstrapApplication(App, appConfig).

Angular looked at the App component and found its selector app-root. It matched this to <app-root> in index.html and rendered app.html inside it.

app.html had four tags — <app-navbar>, <app-hero>, <app-skills>, <app-footer>. Angular looked in App's imports array and found Navbar, Hero, Skills, and Footer. It rendered each one in order.

The Navbar component had a siteName string and a navLinks array in its class. The navbar.html template displayed siteName with {{ siteName }} and looped through navLinks with @for to generate the menu items.

The Skills component had a skills array in its class. The skills.html template used @for to loop through the array and render one card per skill, with a progress bar using [style.width.%]="skill.level" to dynamically set the width.

Each component managed itself completely independently. No component knew anything about the others except that the root App imported all of them.

This is the beauty of component-based architecture. Everything is organized, independent, and reusable.


Phase 2 — Complete Summary

You have covered a lot of ground in this phase. Here is everything you learned:

Single Page Application — Angular loads one HTML file once. All navigation after that is Angular swapping components on screen. No full page reloads, no white flashes, no state loss.

Component tree — Your entire app is a tree of components. The root App component sits at the top. Every other component lives somewhere inside it.

What a component is — Three files working together. A TypeScript file for data and logic, an HTML file for the template, and a CSS file for scoped styles.

Angular project filesindex.html holds <app-root>. main.ts starts the app. app.config.ts holds app-wide configuration. app.routes.ts holds navigation routes. styles.css holds global styles. public/ holds static assets.

Signals — A reactive way to hold values. Create with signal(), read in templates with (), update with .set().

No module files — Angular does not use app.module.ts. Components are standalone and manage their own dependencies.

The golden rule — If you use something in a template, import it in the component's imports array.

Angular file naming — Files are navbar.ts, navbar.html, navbar.css. Class names are Navbar, Hero, Skills — clean and simple, no extra words.

The startup flowmain.tsbootstrapApplicationappConfigApp renders → children render → app is live.

Angular CLIng new, ng serve, ng g c, ng build — the commands you will use every single day.

Built a real project — Complete multi-component portfolio page with proper Angular structure.


What's Next — Phase 3

In Phase 3 we go much deeper into components. You will learn:

All 8 lifecycle hooks — what they are, when each one runs, and when to use which one. You will understand ngOnInit deeply — the most used lifecycle hook for loading data.

@Input() — how to pass data from a parent component down to a child component. This is how components talk to each other.

@Output() and EventEmitter — how a child component sends events back up to its parent.

@ViewChild — how to access a child component directly from a parent's TypeScript code.

Content projection with ng-content — how to build wrapper components that can display content passed from outside.

How Angular handles component styles and keeps them scoped to their own component.

How Angular's change detection works — how Angular knows when something changed and when to update the screen.

Phase 3 — Components Deep Dive

Chapter 1 — What We Are Going to Learn and Why In Phase 2 you learned what a component is and how to create one. You know that a component h...