有時(shí)不得不面對一些需要在組件中直接操作DOM的情況,如我們的組件中存在大量的CheckBox,我們想獲取到被選中的CheckBox缅疟,然而這些CheckBox是通過循環(huán)產(chǎn)生的,我們無法給每一個CheckBox指定一個ID哲戚,這個時(shí)候可以通過操作DOM來實(shí)現(xiàn)。angular API中包含有viewChild艾岂,contentChild等修飾符顺少,這些修飾符可以返回模板中的DOM元素。
指令中的DOM操作
@Directive({
selector: 'p'
})
export class TodoDirective{
constructor(el: ElementRef, renderer: Renderer){
renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'red');
}
}
以上聲明了一個指令王浴,使用是需要在module中的declarations中聲明脆炎。該指令的作用是將p元素的backgroundColor設(shè)置為red。
-ElementRef是一個允許直接獲取DOM元素的一個類氓辣,該類包含一個nativeElement屬性秒裕。當(dāng)不允許直接操作原生DOM元素時(shí),該屬性值為null筛婉。
-Renderer該類包含大量可以用來操作DOM原生的方法簇爆。
@ViewChild和@ViewChildren
每一個組件都有一個視圖模板,通過 template或templateUrl引入爽撒。想要獲取視圖模板中的DOM元素則可以使用@ViewChild和@ViewChildren修飾符。他們可以接受模板變量或元素標(biāo)簽或模板類名來獲取DOM節(jié)點(diǎn)响蓉。@ViewChild返回ElementRef類引用(獲取組件時(shí)則直接使用組件類名)硕勿,而@ViewChildren返回QueryList<ElementRef>。
//模板內(nèi)容
<p *ngFor='let item of todos' #name>{{ item.name }}</p>
//組件中獲取DOM
@ViewChildren('name')
todoNames: QueryList<ElementRef>;
@ViewChild('name')
todoName: ElementRef;
ngAfterViewInit(){
this.todoNames.forEach(e=>console.log(e.nativeElement.innerText));
console.log(this.todoName.nativeElement.innerText);
}
@ViewChild('name')和@ViewChildren('name')通過name模板變量獲取p標(biāo)簽DOM節(jié)點(diǎn)枫甲,可以在ngAfterViewInit聲明周期鉤子中獲取節(jié)點(diǎn)信息源武,當(dāng)然也可以在其他函數(shù)中扼褪,只要保證視圖完成初始化即可。
QueryList是一個不可變的列表粱栖,其存在一個名為changes的Observable變量话浇,因此可以被訂閱,結(jié)合notifyOnChanges方法闹究,可以實(shí)時(shí)查看QueryList中變量的變化幔崖。調(diào)用notifyOnChanges函數(shù)后,當(dāng)組件的輸入發(fā)生變化時(shí)會觸發(fā)Observable發(fā)出新的值渣淤,這樣當(dāng)todoNames: QueryList<ElementRef>有更新時(shí)赏寇,便能通過下面代碼查看到變化:
this.todoNames.changes.subscribe(data => data._results.forEach(
e=>console.log(e.nativeElement.innerText)));
this.todoNames.notifyOnChanges();
@ContentChild和@ContentChildren
看著與@ViewChild和@ViewChildren很相似,但@ContentChild和@ContentChildren是獲取組件標(biāo)簽中的內(nèi)容的价认,懶得寫例子嗅定,這里直接貼上angular中文官網(wǎng)的一個例子:
import {Component, ContentChildren, Directive, Input, QueryList} from '@angular/core';
@Directive({selector: 'pane'})
export class Pane {
@Input() id: string;
}
@Component({
selector: 'tab',
template: <div>panes: {{serializedPanes}}</div>
})
export class Tab {
@ContentChildren(Pane) panes: QueryList<Pane>;
get serializedPanes(): string { return this.panes ? this.panes.map(p => p.id).join(', ') : ''; }
}
@Component({
selector: 'example-app',
template: <tab> <pane id="1"></pane> <pane id="2"></pane> <pane id="3" *ngIf="shouldShow"></pane> </tab> <button (click)="show()">Show 3</button>
,
})
export class ContentChildrenComp {
shouldShow = false;
show() { this.shouldShow = true; }
}
可以看出@ContentChildren(Pane) panes: QueryList<Pane>;獲取的是組件Tab中的內(nèi)容:
<tab>
<pane id="1"></pane>
<pane id="2"></pane>
<pane id="3" *ngIf="shouldShow"></pane>
</tab>
與@ViewChild類似@ContentChild獲取的是第一個Pane指令,獲取DOM元素后用踩,可以采用類似的方式處理渠退。
TemplateRef
模板的概念應(yīng)該是大多數(shù)Web開發(fā)人員熟悉的。在整個應(yīng)用程序的視圖中重復(fù)使用一組DOM元素脐彩。在HTML5標(biāo)準(zhǔn)引入了template標(biāo)簽之前碎乃,大多數(shù)都是用下面這種方式實(shí)現(xiàn),增加type屬性:
<script>
let tpl = document.querySelector('#tpl');
let container = document.querySelector('.insert-after-me');
insertAfter(container, tpl.content);
</script>
<div class="insert-after-me"></div>
<ng-template id="tpl">
<span>I am span in template</span>
</ng-template>
Angular支持這種方法丁屎,并實(shí)現(xiàn)TemplateRef類來處理模板荠锭。以下是如何使用它:
@Component({
selector: 'sample',
template: <ng-template #tpl> <span>I am span in template</span> </ng-template>
})
export class SampleComponent implements AfterViewInit {
@ViewChild("tpl") tpl: TemplateRef<any>;
ngAfterViewInit() {
let elementRef = this.tpl.elementRef;
// outputs `template bindings={}`
console.log(elementRef.nativeElement.textContent);
}
}
Angular從DOM中刪除模板元素,并在其位置插入注釋晨川。這是呈現(xiàn)時(shí)的樣子:
<sample>
</sample>
TemplateRef類本身就是一個簡單的類证九。It holds a reference to its host element in elementRef property and has one method createEmbeddedView。這個方法是非常有用的共虑,因?yàn)樗试S我們創(chuàng)建一個視圖并以ViewRef的形式返回一個引用
ViewRef
Angular鼓勵開發(fā)人員將UI視為Views的組合愧怜,而不是將其視為獨(dú)立的HTML標(biāo)記樹。
Angular支持2種視圖
Embedded Views which are linked to a Template
Host Views which are linked to a Component
Creating embedded view
ngAfterViewInit() {
let view = this.tpl.createEmbeddedView(null);
}
Creating host view
constructor(private injector: Injector,
private r: ComponentFactoryResolver) {
let factory = this.r.resolveComponentFactory(ColorComponent);
let componentRef = factory.create(injector);
let view = componentRef.hostView;
}
ViewContainerRef
表示可以附加一個或多個視圖的容器妈拌。
首先要提到的是拥坛,任何DOM元素都可以用作視圖容器。通常尘分,ViewContainer與ng-container結(jié)合使用猜惋。ng-container會被渲染為一個注釋,所以它不會在DOM中引入多余的html元素培愁。以下是在組件模板的特定位置創(chuàng)建ViewContainer的示例:
@Component({
selector: 'sample',
template: <span>I am first span</span> <ng-container #vc></ng-container> <span>I am last span</span>
})
export class SampleComponent implements AfterViewInit {
@ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef;
ngAfterViewInit(): void {
// outputs `template bindings={}`
console.log(this.vc.element.nativeElement.textContent);
}
}