畫面需要重複利用的內容時,可利用嵌入內容設計 (transclusion) 來達成,是一個內容投射 (content projection) 的概念
Content projection with ng-content (投射全部內容)
如下圖所示,在 app-root 畫面,使用了兩個 auth-form的元件內容,但auth-form的 title 因為寫死了,所以現在畫面都長一樣,有沒有更好的做法呢? 可以使用 ng-content !


使用 ng-content
所以在兩個 auth-form 內寫了<h3> 的內容,然後auth-form 加上 ng-content
如此 ng-content 就會把我們定義的 <h3> 內容投射進去 (因為在左邊我們也沒有定義其他內容)

成功的置換了 title,大功告成

Using ng-content with projection slots (用 select 指定投射內容)
現在我們希望把下面的按鈕也換掉,所以多加了一個按鈕


但會發現按鈕都在上面,因為現在只有使用<ng-content>,所以他會把 “所有” 內容都投射進去
所以如果想要把 title 的位置放上面,按鈕在下面的位置
可以使用 ng-content + select
這個例子

ng-content + select (HTML tag or css selector 語法)
1
| <ng-content select="像是使用 css selector"></ng-content>
|
1 2 3 4 5 6 7
| //用 class 去找 <ng-content select=".card-body"></ng-content>
... <div class="card-block" body card>...</div> ...
|
1 2 3 4 5 6 7
| //用 attibute 去找 <ng-content select="[card][body]"></ng-content>
... <div class="card-block" body card>...</div> ...
|
1 2 3 4 5 6
| //用 HTML TAG 去找 <ng-content select="card-body"></ng-content> ...
<card-body class="card-block">...<card-body> ...
|
Projecting and binding to components (投射 componet)
同樣的概念下,我們也可以把自己做的 component directive 利用 content projection 投射到某個畫面上
這個例子中,我們想要加上一個 remember me 的 component 到畫面上,如下圖

這裡定義一個 auth-remember 的 component directive, 然後使用 ng-content 投射內容
1
| <ng-content select="auth-remember"></ng-content>
|
auth-remember.componet.ts
remember me componet: 提供一個 checkbox 勾選後,會送出是否記得帳號(true or false)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { Component, Output, EventEmitter } from '@angular/core';
@Component({ selector: 'auth-remember', template: ` <label> <input type="checkbox" (change)="onChecked($event.target.checked)"> Keep me logged in </label> ` }) export class AuthRememberComponent {
@Output() checked: EventEmitter<boolean> = new EventEmitter<boolean>();
onChecked(value: boolean) { this.checked.emit(value); }
}
|
auth-form.componet.ts
在第 20 行, 加上了新的 remember me 的 auth-remember component
在 28 行透過 @Output() submitted() 給 父 component: app.componet.ts
讓 app.componet.ts 再去呼叫不同的方法 createUser() or loginUser()
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 29 30 31 32 33 34
| import { Component, Output, EventEmitter } from '@angular/core';
import { User } from './auth-form.interface';
@Component({ selector: 'auth-form', template: ` <div> <form (ngSubmit)="onSubmit(form.value)" #form="ngForm"> <ng-content select="h3"></ng-content> <label> Email address <input type="email" name="email" ngModel> </label> <label> Password <input type="password" name="password" ngModel> </label> <ng-content select="auth-remember"></ng-content> <ng-content select="button"></ng-content> </form> </div> ` }) export class AuthFormComponent {
@Output() submitted: EventEmitter<User> = new EventEmitter<User>();
onSubmit(value: User) { this.submitted.emit(value); }
}
|
app.componet.ts
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| import { Component } from '@angular/core';
import { User } from './auth-form/auth-form.interface';
@Component({ selector: 'app-root', template: ` <div> <auth-form (submitted)="createUser($event)"> <h3>Create account</h3> <button type="submit"> Join us </button> </auth-form> <auth-form (submitted)="loginUser($event)"> <h3>Login</h3> <auth-remember (checked)="rememberUser($event)"> </auth-remember> <button type="submit"> Login </button> </auth-form> </div> ` }) export class AppComponent {
rememberMe: boolean = false;
rememberUser(remember: boolean) { this.rememberMe = remember; }
createUser(user: User) { console.log('Create account', user); }
loginUser(user: User) { console.log('Login', user, this.rememberMe); }
}
|
第一個 沒有
第二個 有 (19~21行) 所以會顯示 remember me
這裡的 btn 都有宣告 type="submit" 所以按下 btn 時, 都會啟動 auth-form component 裡的 form submit
1
| <form (ngSubmit)="onSubmit(form.value)" #form="ngForm">
|
這個 form submit 會呼叫 auth-form Component 的 "onSubmit(form.value)" 方法
1 2 3
| onSubmit(value: User) { this.submitted.emit(value); }
|
"onSubmit(form.value)" 這方法會呼叫 AuthForm Component 定義的 @Output() submitted 事件,
1 2 3
| @Output() submitted: EventEmitter<User> = new EventEmitter<User>();
this.submitted.emit(value);
|
this.submitted.emit(value) emit 事件出去後,會呼叫 app componet (submitted) 事件綁定的方法
所以…
第一個 呼叫 loginUser()
1
| (submitted)="loginUser($event)"
|
第二個 呼叫 rememberUser()
1
| (submitted)="rememberUser($event)"
|
Result

感想
ng-content 是個蠻方便的東西,但最後一個例子其實寫起來好像有點複雜XD…是不是開新帳號和登入頁分開寫還比較簡單哈…
Reference