Angular - ng-content 的用法

Posted by Tim Lin on 2018-12-16

畫面需要重複利用的內容時,可利用嵌入內容設計 (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>

<!-- app.component.html -->
...
<div class="card-block" body card>...</div>
...
1
2
3
4
5
6
7
//用 attibute 去找
<ng-content select="[card][body]"></ng-content>

<!-- app.component.html -->
...
<div class="card-block" body card>...</div>
...
1
2
3
4
5
6
//用 HTML TAG 去找
<ng-content select="card-body"></ng-content>
...
<!-- app.component.html -->
<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