#angular #components #content-projection #reusability

Day 12 — Reusable Components: Content Projection & ViewChild Deep Dive

📘 Day 12 — Reusable Components: Content Projection & ViewChild Deep Dive

Zero to Hero — Hands-on Angular Tutorial

Today you will learn:

  • ✔️ What is Content Projection? (<ng-content>)
  • ✔️ Single-slot vs. Multi-slot projection
  • ✔️ Building a flexible Card Component wrapper
  • ✔️ Accessing the DOM with @ViewChild
  • ✔️ Interacting with child components programmatically

This day teaches you how to build library-quality, reusable UI components that don’t look hardcoded. 🧩


🟦 1. The Problem: Hardcoded Components

Imagine you want a card component. If you hardcode the text inside ChildComponent, it’s useless for anything else.

Bad Way:

// child.component.html
<div class="card">
  <h2>My Title</h2> <!-- Hardcoded ❌ -->
  <p>My Content</p> <!-- Hardcoded ❌ -->
</div>

Goal: We want to pass HTML into the component from the parent.


🟩 2. Solution: ng-content (Single Slot)

ng-content acts as a placeholder. It tells Angular: “Take whatever HTML sits between my tags and put it HERE.”

Example: A Simple Card Wrapper

card.component.html:

<div class="card-styles">
  <ng-content></ng-content> <!-- Angular puts your content here -->
</div>

app.component.html:

<app-card>
  <!-- Everything here gets "projected" inside the card -->
  <h3>User Profile</h3>
  <button>Edit</button>
</app-card>

🟧 3. Multi-Slot Projection (Named Slots)

What if you want a Header, Body, and Footer? You can split the content using select.

card-advanced.component.html:

<div class="card">
  <div class="header">
    <ng-content select="[header]"></ng-content>
  </div>

  <div class="body">
    <ng-content></ng-content> <!-- Default slot (catches everything else) -->
  </div>

  <div class="footer">
    <ng-content select="[footer]"></ng-content>
  </div>
</div>

app.component.html:

<app-card-advanced>
  <!-- Goes to header slot -->
  <h2 header>My Product</h2>

  <!-- Goes to default slot -->
  <p>This is the main product description.</p>

  <!-- Goes to footer slot -->
  <button footer>Buy Now</button>
</app-card-advanced>
  • ✔️ select="[attribute]" targets elements with that attribute.
  • ✔️ select=".class" targets elements with that class.
  • ✔️ select="tag" targets specific HTML tags.

🟥 4. Accessing Components: @ViewChild

Sometimes the Parent needs to control the Child logic (call a method), not just pass data.

Scenario: A specialized VideoPlayer component. The parent wants to click “Play”.

video-player.component.ts:

@Component({...})
export class VideoPlayerComponent {
  play() {
    console.log("Playing video... ▶️");
  }
}

app.component.ts:

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

@Component({...})
export class AppComponent {
  // Grab the child component instance
  @ViewChild(VideoPlayerComponent) player!: VideoPlayerComponent;

  handlePlayClick() {
    // Calling a method on the child!
    this.player.play();
  }
}
  • ✔️ @ViewChild gives you the actual TypeScript class instance of the child.
  • ✔️ Allows strict control/orchestration from parent.

🟫 5. Accessing Native DOM Elements

If you need the raw HTML element (e.g., to scroll to bottom, focus input, or draw on Canvas), use ElementRef.

app.component.html:

<input #myInput type="text" placeholder="I will get focus..." />
<button (click)="focusInput()">Focus It</button>

app.component.ts:

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

export class AppComponent {
  // Read the element as an ElementRef (Wrapper around native DOM)
  @ViewChild('myInput') inputRef!: ElementRef<HTMLInputElement>;

  focusInput() {
    this.inputRef.nativeElement.focus();
    this.inputRef.nativeElement.style.backgroundColor = 'yellow';
  }
}

⚠️ Warning: Avoid direct DOM manipulation (like style.backgroundColor) unless necessary. Use Binding whenever possible. focus() is a valid use case.


🎉 End of Day 12 — What You Learned

Today you learned how to architect cleaner apps:

  • ✔️ Content Projection lets you create “Wrapper” components (Cards, Layouts, Modals).
  • ✔️ Multi-slot Projection organizes complex layouts.
  • ✔️ @ViewChild lets the parent call methods on the child.
  • ✔️ ElementRef gives safe access to raw DOM elements.

🧪 Day 12 Challenge

Build a “Modal Wrapper” Component.

Requirements:

  1. Create ModalComponent.
  2. Use Content Projection so the parent can put any form or text inside it.
  3. Add a generic “Close” button inside the modal logic.
  4. Parent uses @ViewChild to call this.modal.open() and this.modal.close().
  5. Bonus: Add a backdrop (grey background) that closes the modal when clicked.