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:
-
CardComponentStructure:- The
CardComponent's template should include distinct areas for aheader,body, andfooter. - Each of these areas should be designated using
ng-contentwith aselectattribute to identify its purpose. - The
CardComponentshould also have a basic visual styling (e.g., a border, some padding) to make the projected content clearly distinguishable.
- The
-
Content Projection:
- Create a parent component (e.g.,
AppComponent) that uses theCardComponent. - Demonstrate projecting different types of content into the
header,body, andfooterslots of theCardComponent. This could include text, HTML elements, or even other Angular components.
- Create a parent component (e.g.,
-
Default Content:
- Implement a default content projection for the
bodyof theCardComponent. This default content should be displayed only if no content is explicitly projected into thebodyfrom the parent.
- Implement a default content projection for the
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-contenttag in theCardComponent? (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
CardComponentshould have a dedicated CSS file for styling. - The
AppComponentshould be used to demonstrate the usage ofCardComponent. - The
ng-contentdirective must be used to achieve content projection. - The solution should be a standard Angular application structure.
Notes
ng-contentcan be used without aselectattribute to represent the default content projection slot.- The
selectattribute ofng-contentcan 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.