1 为什么要用transclusion(嵌入包含)
不要被术语嵌入包含所迷惑,下面用一个简单的例子将它说明清楚。
现有一个卡片组件(card component),它由header、body和footer三个部分组成。
比如,我们可以这样使用该组件:
或者这样:
亦或者这样
总之非常方便。
问题来了,我们该如何实现**头部和尾部格式固定,而body中的内容可以动态显示呢?
--答案是使用嵌入包含**。
2 什么是transclusion(嵌入包含)
transclusion是一个方法,允许你定义个固定视图模板的同时,还可以通过
<ng-content>
定义一个插槽,以显示动态的内容。
很有意思吧,下面我们就来实现一下。
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:
<!-- 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>
使用组件
现在我们已经定义好了一个组件,接下来我们将使用它。
<!-- 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 ],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愉快!