Angular中ng-template和ng-container的應(yīng)用小結(jié)
Angular的日常工作中經(jīng)常會(huì)使用到ng-template和ng-container,本文對(duì)他們做一個(gè)總結(jié)。
ng-template
ng-template是一個(gè)Angular元素,它不會(huì)直接顯示出來(lái)。在渲染視圖之前,Angular會(huì)把它的內(nèi)容替換為一個(gè)注釋。ng-template是用來(lái)定義模板的,當(dāng)定義好一個(gè)模板之后,可以用ng-container和ngTemplateOutlet指令來(lái)進(jìn)行使用。
簡(jiǎn)單點(diǎn)來(lái)說(shuō),就是定義了一個(gè)模板片段,并且起了一個(gè)名字,在需要的時(shí)候可以通過名字來(lái)使用這個(gè)片段。
<!--定義模板,并不會(huì)在視圖中顯示--> <ng-template #loading> <div class="waiting-wrap"> <div class="spinner">loading...</div> </div> </ng-template> <!--使用模板,在視圖中顯示--> <ng-container *ngTemplateOutlet="loading"></ng-container>
<ng-template #myTemp> <div>Hello, Tom?</div> </ng-template> <div class="container"> <ng-container *ngTemplateOutlet="myTemp"></ng-container> <div [ngTemplateOutlet]="myTemp"> Jerry </div> </div>
作用域的Template
帶作用域的Template,可以從當(dāng)前作用域獲得數(shù)據(jù)。
<ng-template #userTemp let-account="name" let-age="age"> <p>{{account}} - {{age}}</p> </ng-template> <ng-container *ngTemplateOutlet="userTemp; context: userInfo"> </ng-container>
上面的let-account="name",相當(dāng)于定義了一個(gè)account變量,變量的值為 context.name。也就是下面的代碼:
let account = context.name; let age = context.age;
import { AfterViewInit, Component, ComponentFactory, ComponentFactoryResolver, ComponentRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; import { MsgComponent } from '../msg/msg.component'; @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.scss'] }) export class UserComponent implements OnInit, OnChanges, AfterViewInit { userInfo: any; constructor() {} ngOnInit(): void { this.userInfo = { name: 'Tom', age: 21 }; // this.createComponent(); } }
TemplateRef
TemplateRef實(shí)例用于表示模板對(duì)象。如果我在模板里定義了一個(gè)ng-template,想在組件中訪問它,這時(shí)候就用到了TemplateRef。
<!--定義模板,并不會(huì)在視圖中顯示--> <ng-template #myTemp> <div>Hello, Tom?</div> </ng-template>
export class UserComponent implements OnInit, OnChanges, AfterViewInit { @ViewChild('myTemp', { read: TemplateRef}) temp1!: TemplateRef<any>; ...... }
TemplateRef:
Represents an embedded template that can be used to instantiate embedded views. To instantiate embedded views based on a template, use the ViewContainerRef method createEmbeddedView().
TemplateRef就是ng-template的實(shí)例。后面會(huì)說(shuō)怎么和ng-container的實(shí)例搭配使用。
ng-container
ng-container是Angular的一個(gè)特殊標(biāo)簽,它和ng-template完全不同。
- ng-template只是定義了一個(gè)視圖片段的模板,并不會(huì)直接在視圖中展示出來(lái)(會(huì)顯示為注釋);
- ng-container可以在視圖中展示出來(lái),其本身并沒有創(chuàng)建任何節(jié)點(diǎn),只是作為一個(gè)邏輯Tag來(lái)使用;
<ng-template #myTemp> <div>Hello, Tom?</div> </ng-template> <div class="container"> <ng-container #container1> Jerry </ng-container> </div>
在Vue2中,我們定義一個(gè)組件,但是這個(gè)組件的視圖中必須有一個(gè)根元素,如果有多個(gè)根元素,就會(huì)報(bào)錯(cuò)。是因?yàn)?,任何Vue組件的實(shí)例需要綁定到一個(gè)單一DOM元素上。
<template> <!--報(bào)錯(cuò)--> <div> Tom </div> <div> {{age}} </div> </template> <template> <!--正確--> <div> <div> Tom </div> <div> {{age}} </div> </div> </template>
但是有些情況下,我們就是不想有外層元素包裹,那怎么辦呢?Vue3中改善了這一點(diǎn),直接可以寫上面的模板,并不會(huì)報(bào)錯(cuò),原因是在模板編譯的時(shí)候自動(dòng)添加了Fragment虛擬元素。
在React中也會(huì)有同樣的問題,解決方案就是一個(gè)名為Fragment的虛擬元素。
class Columns extends React.Component { render() { return (<React.Fragment> <td>Hello</td> <td>World</td> </React.Fragment> ); } }
盡管Fragment看起來(lái)像一個(gè)普通的DOM元素,但它是虛擬的,根本不會(huì)在DOM樹中呈現(xiàn)。這樣我們可以將組件實(shí)例綁定到一個(gè)單一的元素中,而不需要?jiǎng)?chuàng)建一個(gè)多余的DOM節(jié)點(diǎn)。
Angular中的ng-container可以看做是Vue,React中的Fragment。
<ul> <ng-container *ngFor="let item of listdata"> <li> {{item.id}}---{{item.name}} </li> </ng-container> </ul>
那如果我想在組件代碼中操作ng-container的實(shí)例呢?
場(chǎng)景:比如在一些復(fù)雜場(chǎng)景中,根據(jù)不同情況將不同的ng-template插入到ng-container中。
<ng-template #myTemp> <div>Hello, Tom?</div> </ng-template> <div class="container"> <ng-container #container1> Jerry </ng-container> </div>
export class UserComponent implements OnInit, OnChanges, AfterViewInit { // 需要加上{ read: TemplateRef}, { read: ViewContainerRef} // 否則會(huì)當(dāng)成普通element @ViewChild('myTemp', { read: TemplateRef}) temp1!: TemplateRef<any>; @ViewChild('container1', { read: ViewContainerRef}) container1!: ViewContainerRef; ngAfterViewInit(): void { console.log(this.temp1); this.container1?.createEmbeddedView(this.temp1); } }
動(dòng)態(tài)創(chuàng)建Component
說(shuō)到ng-container,就不得不提下動(dòng)態(tài)創(chuàng)建Component。
現(xiàn)在有一個(gè)組件MsgComponent,我們想在UserComponent中動(dòng)態(tài)創(chuàng)建出這個(gè)Component。
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-msg', template: '<h2>消息:{{msg}}</h2>' }) export class MsgComponent implements OnInit { msg?: string = 'Hello'; constructor() { } ngOnInit() { } }
還要把這個(gè)MsgComponent在NgModule的entryComponents里注冊(cè)一下。當(dāng)然也可以在Component的entryComponents里注冊(cè)一下也行。
@NgModule({ declarations: [ AppComponent, UserComponent ], imports: [ BrowserModule ], entryComponents: [ MsgComponent ], bootstrap: [AppComponent] }) export class AppModule { }
在User組件的template中,需要指定一個(gè)“放”MsgComponent的地方,也就是ng-container。
<div class="container"> <ng-container #container1> Jerry <p>Tom</p> </ng-container> </div>
import { AfterViewInit, Component, ComponentFactory, ComponentFactoryResolver, ComponentRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; import { MsgComponent } from '../msg/msg.component'; @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.scss'] }) export class UserComponent implements OnInit, OnChanges, AfterViewInit { @ViewChild('container1', { read: ViewContainerRef, static: true}) container1!: ViewContainerRef; constructor(private _resolveSvc: ComponentFactoryResolver) { } ngOnInit(): void { // console.log('user: ngOnInit'); // this.userID = this.options.id; this.createComponent(); } createComponent(): void { let fac: ComponentFactory<MsgComponent> = this._resolveSvc.resolveComponentFactory(MsgComponent); // this.container1.clear(); let msgComp: ComponentRef<MsgComponent> = this.container1.createComponent(fac); msgComp.instance.msg = '動(dòng)態(tài)創(chuàng)建Component了!'; } ngAfterViewInit(): void { // this.createComponent(); // { static: false} } }
ComponentFactoryResolver是Angular里組件工廠解析器,把這個(gè)Service注入進(jìn)來(lái)就可以使用了。resolveComponentFactory接收一個(gè)Component類型,生成一個(gè)ComponentFactory對(duì)象,為什么需要這個(gè)ComponentFactory,直接用Component類型不行嗎?因?yàn)橐粋€(gè)類型其實(shí)就是一個(gè)Class類,不足以描述一個(gè)Component,Component還有selector,inputs,output等,而這個(gè)ComponentFactory就包含了這些信息。這里用到了設(shè)計(jì)模式中的“工廠模式”,輸入一個(gè)Component Class,輸出一個(gè)對(duì)應(yīng)于這個(gè)ComponentFactory具體信息。
createComponent比較好理解了,基于ComponentFactory具體信息,創(chuàng)建一個(gè)Component,并把這個(gè)Component添加到指定的容器里,還可以指定添加的位置。返回值是組件對(duì)象,注意這里是組件對(duì)象,并不是組件Class的實(shí)例,Class的實(shí)例在組件對(duì)象的instance屬性上。
如果有需要,可以清空容器。
this.container1.clear();
這里的{ static: true}并不是必須的,也可以不寫static,那就用默認(rèn)值false。這個(gè)static之前說(shuō)過,如果是false的話,由于在ngOnInit生命周期鉤子的時(shí)刻比較早,所以這時(shí)候無(wú)法訪問到this.container1,就會(huì)報(bào)錯(cuò)。那static是false的情況下,應(yīng)該做實(shí)現(xiàn)呢?可以在ngAfterViewInit這個(gè)鉤子中實(shí)現(xiàn)。
ngAfterViewInit(): void { this.createComponent(); }
但是不建議上面的寫法,因?yàn)樵趎gAfterViewInit里說(shuō)明View已經(jīng)完成,這里不建議再次操作View,會(huì)影響性能,盡管這樣并不會(huì)報(bào)錯(cuò)。那我就是想在這里再次操作View呢?可以把更新推遲到下一個(gè)更新周期。
ngAfterViewInit(): void { setTimeout(() => { this.createComponent(); }); asyncScheduler.schedule(() => { this.createComponent(); }); }
有沒有感覺上面動(dòng)態(tài)創(chuàng)建Component的過程有點(diǎn)復(fù)雜,開發(fā)者其實(shí)并不關(guān)心ComponentFactoryResolver,ComponentFactory這些細(xì)節(jié),給你一個(gè)Component類型,給我創(chuàng)建出來(lái)Component實(shí)例就行了。在Angular13中,就對(duì)這個(gè)地方做了簡(jiǎn)化,不在需要ComponentFactoryResolver,ComponentFactory了。
import { AfterViewInit, Component, ComponentFactory, ComponentFactoryResolver, ComponentRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; import { MsgComponent } from '../msg/msg.component'; @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.scss'] }) export class UserComponent implements OnInit, OnChanges, AfterViewInit { @ViewChild('container1', { read: ViewContainerRef, static: true}) container1!: ViewContainerRef; // 無(wú)需注入ComponentFactoryResolver constructor() { } ngAfterViewInit(): void { // this.createComponent(); } ngOnInit(): void { this.createComponent(); } createComponent(): void { // V13簡(jiǎn)化 let msgComp: ComponentRef<MsgComponent> = this.container1.createComponent(MsgComponent); msgComp.instance.msg = 'V13動(dòng)態(tài)創(chuàng)建Component了!'; } }
大功告成!
到此這篇關(guān)于Angular中ng-template和ng-container的應(yīng)用小結(jié)的文章就介紹到這了,更多相關(guān)Angular中ng-template和ng-container內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
實(shí)踐中學(xué)習(xí)AngularJS表單
表單是最常用的一種組建。在Angular.js中,其實(shí)并沒有單獨(dú)的為表單添加多少特殊功能。但是,利用Angular.js框架本身的特點(diǎn),可以更友好的呈現(xiàn)表單。下面將介紹幾種常用的功能在Angular中是如何巧妙實(shí)現(xiàn)的2016-03-03Angularjs+bootstrap+table多選(全選)支持單擊行選中實(shí)現(xiàn)編輯、刪除功能
這篇文章主要介紹了Angularjs bootstrap table多選(全選)支持單擊行選中實(shí)現(xiàn)編輯、刪除功能,需要的朋友可以參考下2017-03-03使用AngularJS處理單選框和復(fù)選框的簡(jiǎn)單方法
這篇文章主要介紹了使用AngularJS處理單選框和復(fù)選框的方法,在AngularJS表單的基礎(chǔ)之上編寫起來(lái)非常簡(jiǎn)單,需要的朋友可以參考下2015-06-06詳解在Angular4中使用ng2-baidu-map的方法
這篇文章主要介紹了在Angular4中使用ng2-baidu-map的方法,本文分步驟給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-06-06AngularJS ng-change 指令的詳解及簡(jiǎn)單實(shí)例
本文主要介紹AngularJS ng-change 指令,這里對(duì)ng-change指令資料做了詳細(xì)介紹,并提供源碼和運(yùn)行結(jié)果,有需要的小伙伴參考下2016-07-07自定義Angular指令與jQuery實(shí)現(xiàn)的Bootstrap風(fēng)格數(shù)據(jù)雙向綁定的單選與多選下拉框
這篇文章主要介紹了自定義Angular指令與jQuery實(shí)現(xiàn)的Bootstrap風(fēng)格數(shù)據(jù)雙向綁定的單選與多選下拉框 的相關(guān)資料,需要的朋友可以參考下2015-12-12