畫面需要重複利用的內容時,可利用嵌入內容設計 (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>
 | 
| 12
 3
 4
 5
 6
 7
 
 | //用 class 去找<ng-content select=".card-body"></ng-content>
 
 
 ...
 <div class="card-block" body card>...</div>
 ...
 
 | 
| 12
 3
 4
 5
 6
 7
 
 | //用 attibute 去找<ng-content select="[card][body]"></ng-content>
 
 
 ...
 <div class="card-block" body card>...</div>
 ...
 
 | 
| 12
 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)
| 12
 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()
| 12
 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
| 12
 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)" 方法
| 12
 3
 
 | onSubmit(value: User) {this.submitted.emit(value);
 }
 
 | 
"onSubmit(form.value)" 這方法會呼叫 AuthForm Component 定義的 @Output() submitted 事件,
| 12
 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