【Angular 2+】使用實現嵌入包含(transclusion)

1 爲何要用transclusion(嵌入包含)

不要被術語嵌入包含所迷惑,下面用一個簡單的例子將它說明清楚。html

現有一個卡片組件(card component),它由headerbodyfooter三個部分組成。json

  • 該卡片的佈局(3個部分)和顏色(header和footer的背景色爲灰色)是固定的
  • header和footer只容許文本內容;
  • body中能夠是任何形式的內容。

clipboard.png

好比,咱們能夠這樣使用該組件:bootstrap

clipboard.png

或者這樣:app

clipboard.png

亦或者這樣佈局

clipboard.png

總之很是方便。this

問題來了,咱們該如何實現**頭部和尾部格式固定,而body中的內容能夠動態顯示呢?
--答案是使用嵌入包含**。spa

2 什麼是transclusion(嵌入包含)

transclusion是一個方法,容許你定義個固定視圖模板的同時,還能夠經過 <ng-content>定義一個插槽,以顯示動態的內容。

頗有意思吧,下面咱們就來實現一下。code

3 實現嵌入包含transclusion

3.1 App結構

|- app/
    |- app.component.html
    |- app.component.ts
    |- app.module.ts
    |- card.component.ts
    |- card.component.html
    |- main.ts
|- index.html
|- systemjs.config.js
|- tsconfig.json

3.2 單插槽的嵌入包含

定義組件

// card.component.ts

import { Component, Input, Output } from '@angular/core';
@Component({
  selector: 'card',
  templateUrl: 'card.component.html',
})
export class CardComponent {
    @Input() header: string = 'this is header';   
    @Input() footer: string = 'this is footer';
}

組件模板template:component

<!-- card.component.html -->

<div class="card">
    <div class="card-header">
        {{ header }}
    </div>

    <!-- single slot transclusion here -->
    <ng-content></ng-content>

    <div class="card-footer">
        {{ footer }}
    </div>
</div>

使用組件

如今咱們已經定義好了一個組件,接下來咱們將使用它。orm

<!-- app.component.html -->

<h1>Single slot transclusion</h1>
<card header="my header" footer="my footer">
    <!-- put your dynamic content here -->
    <div class="card-block">
        <h4 class="card-title">You can put any content here</h4>
        <p class="card-text">For example this line of text and</p>
        <a href="#" class="btn btn-primary">This button</a>
      </div>
      <!-- end dynamic content -->
<card>

最後在根模塊的declarations中聲明添加便可。

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }   from './app.component';
import { CardComponent } from './card.component'; // import card component

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent, CardComponent ], // add in declaration
  bootstrap:    [ AppComponent ],
})

export class AppModule { }

好了,大功告成,保存並運行吧。div.card-block中的內容將會代替<ng-content></ng-content>

3.3 插槽的選擇器

<ng-content>接受一個 select屬性,讓插槽具備選擇性。
<!-- card.component.html -->

<div class="card">
    <div class="card-header">
        {{ header }}
    </div>

    <!-- add the select attribute to ng-content -->
    <ng-content select="[card-body]"></ng-content>

    <div class="card-footer">
        {{ footer }}
    </div>
</div>

注意,咱們添加了select=[card-body],這裏意思是「讓包含card-body屬性的元素取代我」。
接着,在html中添加card-body屬性。

<!-- app.component.html -->

<h1>Single slot transclusion</h1>
<card header="my header" footer="my footer">

    <div class="card-block" card-body><!--  We add the card-body attribute here -->
        <h4 class="card-title">You can put any content here</h4>
        <p class="card-text">For example this line of text and</p>
        <a href="#" class="btn btn-primary">This button</a>
      </div>

<card>

保存並運行,一切照常運行。
如今,若是移除card-body,卡片body什麼也顯示不出來,那是由於咱們定義的<ng-content>具備選擇性--只有帶有card-body屬性的元素才能夠代替插槽。

3.4 強大的選擇器

<ng-content>select屬性很是強大。舉幾個例子,

3.4.1 帶值的屬性

<!-- card.component.html -->
...
<ng-content select="[card-type=body]"></ng-content>
...

3.4.2 使用CSS選擇器

card.component.html

...
<ng-content select=".card-body"></ng-content>
...

app.component.html

...
<div class="card-block card-body">...</div>
...

除此以外,例如select=[card][body],selector=".card.body"

3.4.3 使用HTML標籤

card.component.html

...
<ng-content select="card-body"></ng-content>
...

app.component.html

...
<card-body class="card-block">...<card-body>
...

可是,你會遇到一個錯誤:Unhandled Promise rejection: Template parse errors: 'card-body' is not a known element
Angular 2不認識card-body標籤,它既不是指令,也不是組件。一個快速回避該錯誤的方法是:在模塊元數據中添加屬性schemas,以下:

// app.module.ts

import { NgModule, NO_ERRORS_SCHEMA }      from '@angular/core'; //
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }   from './app.component';
import { CardComponent } from './card.component';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent, CardComponent ],
  bootstrap:    [ AppComponent ],
  schemas:      [ NO_ERRORS_SCHEMA ] // add this line
})

export class AppModule { }

3.5 多插槽的嵌入包含

<!-- card.component.html -->

<div class="card">
    <div class="card-header">
    <!-- header slot here -->
        <ng-content select="card-header"></ng-content>
    </div>
    <!-- body slot here -->
    <ng-content select="card-body"></ng-content>
    <div class="card-footer">
    <!-- footer -->
        <ng-content select="card-footer"></ng-content>
    </div>
</div>

app

<!-- app.component.html -->

<h1>Multi slot transclusion</h1>
<card>
    <!-- header -->
    <card-header>
        New <strong>header</strong>
    </card-header>

    <!-- body -->
    <card-body>
        <div class="card-block">
            <h4 class="card-title">You can put any content here</h4>
            <p class="card-text">For example this line of text and</p>
            <a href="#" class="btn btn-primary">This button</a>
          </div>
    </card-body>

    <!-- footer -->
    <card-footer>
        New <strong>footer</strong>
    </card-footer>
<card>

4 總結

咱們應該使用哪一個選擇器呢?屬性選擇器?html標籤?類選擇器?

視狀況而定。我更偏向於使用屬性選擇器,由於它更易讀。HTML標籤易讀但須要在元數據中添加schema屬性。

應該儘量避免使用類選擇器,由於不直觀。你第一眼看到它時,第一反應不是「嵌入包含」,除非你閱讀了組件的源碼。固然了,這都取決於你!

好了,就到這裏!祝coding愉快!

相關文章
相關標籤/搜索