Hone logo
Hone
Problems

Mastering Content Projection with ng-content in Angular

Angular's ng-content directive is a fundamental tool for building reusable and flexible components. It allows you to project content from a parent component into specific locations within a child component's template. This challenge will test your understanding of how to effectively use ng-content to create dynamic and modular UI elements.

Problem Description

Your task is to create a reusable Angular component named CardComponent that utilizes ng-content to display dynamic content. The CardComponent should have a predefined structure (e.g., a header, a body, and a footer) where different pieces of content can be projected from its parent.

Key Requirements:

  1. CardComponent Structure:

    • The CardComponent's template should include distinct areas for a header, body, and footer.
    • Each of these areas should be designated using ng-content with a select attribute to identify its purpose.
    • The CardComponent should also have a basic visual styling (e.g., a border, some padding) to make the projected content clearly distinguishable.
  2. Content Projection:

    • Create a parent component (e.g., AppComponent) that uses the CardComponent.
    • Demonstrate projecting different types of content into the header, body, and footer slots of the CardComponent. This could include text, HTML elements, or even other Angular components.
  3. Default Content:

    • Implement a default content projection for the body of the CardComponent. This default content should be displayed only if no content is explicitly projected into the body from the parent.

Expected Behavior:

When the AppComponent renders, the CardComponent should correctly display the projected content in its respective header, body, and footer sections. If no content is provided for the body, the default content should be visible.

Edge Cases:

  • What happens if content is projected into a slot that doesn't have a corresponding ng-content tag in the CardComponent? (The unprojected content will simply not appear).
  • Ensure the default content for the body only shows when no explicit content is projected.

Examples

Example 1:

card.component.html

<div class="card">
  <div class="card-header">
    <ng-content select="[card-title]"></ng-content>
  </div>
  <div class="card-body">
    <ng-content select="[card-content]"></ng-content>
    <ng-content></ng-content> <!-- Default content for body -->
  </div>
  <div class="card-footer">
    <ng-content select="[card-actions]"></ng-content>
  </div>
</div>

card.component.ts

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

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.css']
})
export class CardComponent { }

card.component.css

.card {
  border: 1px solid #ccc;
  padding: 15px;
  margin-bottom: 15px;
  border-radius: 5px;
}

.card-header {
  font-weight: bold;
  margin-bottom: 10px;
  border-bottom: 1px solid #eee;
  padding-bottom: 5px;
}

.card-body {
  margin-bottom: 10px;
}

.card-footer {
  font-style: italic;
  color: #666;
  border-top: 1px solid #eee;
  padding-top: 5px;
}

app.component.html

<app-card>
  <h3 card-title>My Awesome Card</h3>
  <p card-content>This is the main content of the card.</p>
  <button card-actions>Action Button</button>
</app-card>

<app-card>
  <h3 card-title>Card with Only Title</h3>
  <!-- No content projected into card-content or default slot -->
</app-card>

Output:

The first app-card will render with:

  • Header: "My Awesome Card"
  • Body: "This is the main content of the card."
  • Footer: "Action Button"

The second app-card will render with:

  • Header: "Card with Only Title"
  • Body: Displays the default content (which in this case is nothing if not provided, or whatever you define as default). For this example, let's assume the default content is an empty string if not provided.
  • Footer: Empty.

Explanation:

The card-title, card-content, and card-actions attributes act as selectors, directing the projected HTML into the corresponding ng-content tags within CardComponent. The second ng-content without a select attribute in CardComponent acts as a fallback for the body.

Example 2: Demonstrating Default Content

app.component.html (Continuing from Example 1)

<app-card>
  <h3 card-title>Card with Default Body Content</h3>
  <!-- No content projected for card-content or the default slot -->
</app-card>

card.component.html (Modified to show default content)

<div class="card">
  <div class="card-header">
    <ng-content select="[card-title]"></ng-content>
  </div>
  <div class="card-body">
    <ng-content select="[card-content]"></ng-content>
    <ng-content>
      <p>This is the default body content!</p>
    </ng-content>
  </div>
  <div class="card-footer">
    <ng-content select="[card-actions]"></ng-content>
  </div>
</div>

Output:

The third app-card will render with:

  • Header: "Card with Default Body Content"
  • Body: "This is the default body content!"
  • Footer: Empty.

Explanation:

Since no content was provided for card-content or the default ng-content slot in the parent for the third card, the default content defined within the ng-content tag in card.component.html is displayed.

Constraints

  • You must use TypeScript for your Angular components.
  • The CardComponent should have a dedicated CSS file for styling.
  • The AppComponent should be used to demonstrate the usage of CardComponent.
  • The ng-content directive must be used to achieve content projection.
  • The solution should be a standard Angular application structure.

Notes

  • ng-content can be used without a select attribute to represent the default content projection slot.
  • The select attribute of ng-content can accept CSS selectors, including attribute selectors (e.g., [my-attribute]), element selectors (e.g., div), and class selectors (e.g., .my-class).
  • Consider how you might handle multiple default content slots if your component design required it.
  • Think about the accessibility implications of the content you project.
Loading editor...
typescript