We use cookies to enhance your experience on the site
CodeWorlds

Advanced Patterns - The Summit of Mount Fuji

At the summit of Mount Fuji 富士山 - the sacred mountain of Japan - you will learn the highest techniques of swordsmanship. This is the ultimate trial before earning the title of Angular master!

Content Projection (ng-content)

Content Projection is like the technique of passing a sword to the next successor - it allows components to accept arbitrary content:

1// Simple slot
2@Component({
3  selector: 'app-panel',
4  template: \`
5    <div class="panel">
6      <ng-content></ng-content>
7    </div>
8  \`
9})
10export class PanelComponent {}
11
12// Usage
13<app-panel>
14  <p>Any content</p>
15</app-panel>

Multi-Slot Content Projection

1@Component({
2  selector: 'app-card',
3  template: \`
4    <article class="card">
5      <header class="card-header">
6        <ng-content select="[card-header]"></ng-content>
7      </header>
8
9      <main class="card-body">
10        <ng-content></ng-content>
11      </main>
12
13      <footer class="card-footer">
14        <ng-content select="[card-footer]"></ng-content>
15      </footer>
16    </article>
17  \`
18})
19export class CardComponent {}
20
21// Usage
22<app-card>
23  <h2 card-header>Samurai Profile</h2>
24
25  <p>Main card content - samurai description...</p>
26  <img src="samurai.jpg" alt="Samurai">
27
28  <div card-footer>
29    <button>Edit</button>
30    <button>Delete</button>
31  </div>
32</app-card>

Conditional Content with @if

1@Component({
2  selector: 'app-expandable',
3  template: \`
4    <div class="header" (click)="toggle()">
5      <ng-content select="[header]"></ng-content>
6      <span>{{ isExpanded() ? '▼' : '▶' }}</span>
7    </div>
8
9    @if (isExpanded()) {
10      <div class="content">
11        <ng-content></ng-content>
12      </div>
13    }
14  \`
15})
16export class ExpandableComponent {
17  isExpanded = signal(false);
18
19  toggle(): void {
20    this.isExpanded.update(v => !v);
21  }
22}

ContentChild and ContentChildren

1@Component({
2  selector: 'app-tabs',
3  template: \`
4    <div class="tab-headers">
5      @for (tab of tabs(); track tab.title) {
6        <button
7          [class.active]="tab === activeTab()"
8          (click)="selectTab(tab)">
9          {{ tab.title }}
10        </button>
11      }
12    </div>
13    <div class="tab-content">
14      <ng-content></ng-content>
15    </div>
16  \`
17})
18export class TabsComponent implements AfterContentInit {
19  @ContentChildren(TabComponent) tabsList!: QueryList<TabComponent>;
20
21  tabs = signal<TabComponent[]>([]);
22  activeTab = signal<TabComponent | null>(null);
23
24  ngAfterContentInit(): void {
25    this.tabs.set(this.tabsList.toArray());
26    if (this.tabs().length > 0) {
27      this.selectTab(this.tabs()[0]);
28    }
29  }
30
31  selectTab(tab: TabComponent): void {
32    this.tabs().forEach(t => t.isActive.set(false));
33    tab.isActive.set(true);
34    this.activeTab.set(tab);
35  }
36}
37
38@Component({
39  selector: 'app-tab',
40  template: \`
41    @if (isActive()) {
42      <ng-content></ng-content>
43    }
44  \`
45})
46export class TabComponent {
47  @Input() title = '';
48  isActive = signal(false);
49}
Go to CodeWorlds