如何實作 Angular 的內容投影 - Content Projection

Posted by Ryan Tseng on 2019-04-06

緣起

今天會講這個主題,主要是之前看到很多框架的元件裡面都會有這樣的做法

以下範例取自 ngx-admin 的 nebular 專案,有興趣的讀者可以去看一下它的專案

1
2
3
4
5
6
7
8
<nb-tabset>
<nb-tab tabTitle="Simple Tab #1">
Tab content 1
</nb-tab>
<nb-tab tabTitle="Simple Tab #2">
Tab content 2
</nb-tab>
</nb-tabset>

那實際上這是怎麼樣達成的呢?

我們先從裡面看到外面,因為這個元件同時用到了 單一內容投影, 多筆內容投影

裡面因為稍微單純一點就從裡面開始說!

NbTabComponent

通常看到這種形式,就是你自訂的元件中間包著其他內容,我們就可以猜到他有用到內容投影的技巧,簡單來說你自定的元件標籤中所包起來的那一塊,都會被投影到實際 nb-tab 的元件裡。

1
2
3
4
5
6
7
8
9
10
11
12
<nb-tabset>
<nb-tab tabTitle="Simple Tab #1">
<!-- 投影的內容區塊 start -->
Tab content 1
<!-- 投影的內容區塊 end -->
</nb-tab>
<nb-tab tabTitle="Simple Tab #2">
<!-- 投影的內容區塊 start -->
Tab content 2
<!-- 投影的內容區塊 end -->
</nb-tab>
</nb-tabset>

整塊的 p 標籤會被投到我下面標示的 ng-content 標籤 (ng-content 整個會被取代掉)

1
2
3
4
5
6
7
8
9
10
11
@Component({
selector: 'nb-tab',
template: `
<ng-container *ngIf="init">
<!-- 投影的內容區塊 -->
<ng-content></ng-content>
<!-- 投影的內容區塊 -->
</ng-container>
`,
})
export class NbTabComponent { ... }

NbTabsetComponent

有了上面的概念之後你就知道, nb-tabset 元件裡面包的內容都會投影到

<ng-content select="nb-tab"></ng-content>

這個區塊,唯一不一樣的是,他算是 多筆 的內容投影

所以你會看到 select="ng-tab" 這種特殊的寫法

那是因為他要精確的告訴元件,我要投影的是哪個標籤 or 元素

1
2
3
4
5
6
7
8
9
10
<nb-tabset>
<!-- 投影的區塊 start -->
<nb-tab tabTitle="Simple Tab #1">
Tab content 1
</nb-tab>
<nb-tab tabTitle="Simple Tab #2">
Tab content 2
</nb-tab>
<!-- 投影的區塊 end -->
</nb-tabset>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Component({
selector: 'nb-tabset',
styleUrls: ['./tabset.component.scss'],
template: `
<ul class="tabset">
<li *ngFor="let tab of tabs"
(click)="selectTab(tab)"
[class.responsive]="tab.responsive"
[class.active]="tab.active"
[class.disabled]="tab.disabled"
[attr.tabindex]="tab.disabled ? -1 : 0"
class="tab">
<a href (click)="$event.preventDefault()" tabindex="-1">
<i *ngIf="tab.tabIcon" [class]="tab.tabIcon"></i>
<span *ngIf="tab.tabTitle">{{ tab.tabTitle }}</span>
</a>
<nb-badge *ngIf="tab.badgeText"
[text]="tab.badgeText"
[status]="tab.badgeStatus"
[position]="tab.badgePosition">
</nb-badge>
</li>
</ul>
<!-- 這個區塊就是要投影所挖的洞 -->
<ng-content select="nb-tab"></ng-content>
`,
})
export class NbTabsetComponent implements AfterContentInit { ... }

實例展示

看完了 Angular 的 Content Projection 實際用 stackblitz 練習一下,各位可以點過去玩一下

參考資料