Angular 2 中的 ViewChildren 和 ContentChildren

在這篇文章中易猫,我將解釋在Angular 2中view children和content children之間的區(qū)別孽水,我們將看看如何從父組件來訪問這兩種不同的children,除了這些內(nèi)容勺拣,我們也將提到在@Component
裝飾器中提供的屬性providers
和viewProviders
之間的區(qū)別褥紫。
你可以在我的github找到這篇文章的源代碼树酪,所以讓我們開始吧择镇!
組合基元(Composing primitives)
首先压恒,讓我們弄清Angular2組件(Component)和指令(Directive)概念之間的關(guān)系影暴。組合模式是一個(gè)典型的設(shè)計(jì)模式用來開發(fā)用戶界面。它使我們能夠組成不同的基元涎显,并以相同的方式對(duì)待它們坤检。在函數(shù)式編程的世界中,我們可以組合函數(shù)期吓。 例如:
map ((*2).(+1)) [1, 2, 3]-- [4,6,8]

上面是Haskell的代碼早歇,我們組合函數(shù)(*2)
和(+1),以便后面的列表中每個(gè)項(xiàng)目n被共同操作,操作順序?yàn)閚 -> + 1 -> * 2讨勤。
UI中的組成
那么箭跳,在用戶界面,實(shí)際上相當(dāng)類似的潭千,我們可以將各個(gè)組件當(dāng)做函數(shù)谱姓,這些函數(shù)可以被組合在一起,作為結(jié)果,我們得到更多復(fù)雜的功能。


在上圖中刨晴,我們有2個(gè)元素
Directive - 一個(gè)獨(dú)立的持有一些邏輯的元素,但不包含任何結(jié)構(gòu)屉来。
Component - 一個(gè)元素,它指定了Directive元素狈癞,并擁有其他指令實(shí)例的列表(這也可能是Component茄靠,因?yàn)镃omponent繼承Direcitve)。

這意味著蝶桶,使用上述的抽象概念慨绳,我們可以構(gòu)建以下形式的結(jié)構(gòu):


Angular 2中組件的組合
現(xiàn)在,為了更具體地說明真竖,讓我們切換到Angular 2的上下文脐雪。
// ...@Component({ selector: 'todo-app', providers: [TodoList], directives: [TodoCmp, TodoInputCmp], template: <section> Add todo: <todo-input (onTodo)="addTodo($event)"></todo-input> </section> <section> <h4 *ngIf="todos.getAll().length">Todo list</h4> <todo *ngFor="var todo of todos.getAll()" [todo]="todo"> </todo> </section> <ng-content select="footer"></ng-content>})class TodoAppCmp { constructor(private todos: TodoList) {} addTodo(todo) { this.todos.add(todo); }}// ...

是的,這將是“另一個(gè)MV* todo應(yīng)用”。上面我們定義了一個(gè)組件selector是todo-app,它有一些內(nèi)聯(lián)模板恢共,定義了一組指令战秋,它或它的任何子組件可以使用。
我們可以以下列方式使用組件:
<todo-app></todo-app>

這基本上是一個(gè)XML旁振,因?yàn)樵?lt;todo-app>
和</todo-app>
之間我們可以添加一些內(nèi)容:
<todo-app> <footer> Yet another todo app! </footer></todo-app>

ng-content
讓我們先回到todo-app組件的定義获询,請(qǐng)注意模板的最后一行元素<ng-content select="footer"></ng-content>
,ng-content會(huì)替換 `和之間內(nèi)容到模板內(nèi)涨岁,select屬性的值是一個(gè)CSS選擇器,這使得我們可以選擇我們想要投射的內(nèi)容,例如在上面的例子中吉嚣,footer將在所渲染的todo組件的底部被注入梢薪。
我們也可以不是用select
屬性,在這種情況下尝哆,我們將投射<todo-app>
和</todo-app>
之間的所有內(nèi)容秉撇。
這里的實(shí)現(xiàn)很有很多組件,我們?cè)谶@里并不不需要關(guān)心它們是怎么樣實(shí)現(xiàn)秋泄,所以在這里忽略它們琐馆,應(yīng)用的最終結(jié)果將是如下:

gif

ViewChildren和ContentChildren
是的,它是那么的簡(jiǎn)單恒序,現(xiàn)在瘦麸,我們已經(jīng)準(zhǔn)備好來定義什么是view children和content children的概念。
在組件的模板中定義的內(nèi)容歧胁,它是組件的一部分滋饲,被稱為view children
在host元素<opening>
和</closing>
標(biāo)簽中的被稱為content children

這意味著,在todo-app中的todo-input
和todo
是view children,而footer
(如果它被定義為Angular 2組件或指令)為content child喊巍。
訪問 View 和 Content Children
現(xiàn)在到了有趣的部分屠缭!讓我們看看我們?nèi)绾文軌蛟L問和操作這兩種類型的Children!
不同方式訪問View Children
Angular 2 在 angular2/core
包中提供了下列屬性裝飾器:@ViewChildren
, @ViewChild
, @ContentChildren
和 @ContentChild
崭参。
我們可以通過以下方式:
import {ViewChild, ViewChildren, Component...} from 'angular2/core';// ...@Component({ selector: 'todo-app', providers: [TodoList], directives: [TodoCmp, TodoInputCmp], template: ...})class TodoAppCmp { @ViewChild(TodoInputCmp) inputComponent: TodoInputCmp @ViewChildren(TodoCmp) todoComponents: QueryList<TodoCmp>; constructor(private todos: TodoList) {} ngAfterViewInit() { // available here }}// ...

上面例子顯示我們?nèi)绾问褂聾ViewChildren
和@ViewChild
,基本上呵曹,我們可以裝飾一個(gè)屬性,這樣它會(huì)來查詢視圖中的某個(gè)元素何暮,在上面例子中奄喂,使用@ViewChild
查詢TodoInputCmp子組件和使用@ViewChildren
查詢TodoCmp,我們使用不同的裝飾器海洼,是因?yàn)槲覀冎挥幸粋€(gè)input砍聊,所以我們用@ViewChild
來獲取它,而我們有多個(gè)todo項(xiàng)贰军,所以我們需要使用@ViewChildren

另外要注意的是的inputComponent和todoComponents的屬性類型蟹肘,第一個(gè)屬性的類型是TodoInputCmp词疼,如果Angular在組件控制器實(shí)例中還未發(fā)現(xiàn)該子組件或者引用,則它的值為空帘腹。另一方面贰盗,我們有多個(gè)TodoCmp實(shí)例,并可以從視圖中動(dòng)態(tài)的添加或移除阳欲,所以todoComponents屬性的類型是QueryList<TodoCmp>
,我們可以認(rèn)為QueryList
作為一個(gè)觀察的集合舵盈,一旦項(xiàng)目被添加或刪除它可以發(fā)出事件陋率。
由于Angular的DOM編譯todo-app
組件在它的子組件inputComponent實(shí)例化和todosComponent實(shí)例化之前,所以在todo-app
初始化時(shí)inputComponent
和todoComponents
不會(huì)被設(shè)置秽晚,它們的值要在ngAfterViewInit生命周期鉤子被觸發(fā)后才被設(shè)置瓦糟。
訪問Content Children
幾乎是相同的規(guī)則來訪問content children,但是赴蝇,也有一些輕微的差別菩浙。為了更好地說明它們,讓我們一起來看看它的使用:
@Component({ selector: 'footer', template: '<ng-content></ng-content>'})class Footer {}@Component(...)class TodoAppCmp {...}@Component({ selector: 'app', styles: [ 'todo-app { margin-top: 20px; margin-left: 20px; }' ], template: <content> <todo-app> <footer> <small>Yet another todo app!</small> </footer> </todo-app> </content>, directives: [TodoAppCmp, NgModel, Footer]})export class AppCmp {}

在上面的代碼段句伶,我們定義了兩個(gè)組件Footer和AppCmp劲蜻,F(xiàn)ooter可視化其host元素標(biāo)簽內(nèi)的所有內(nèi)容(<footer>content to be projected</footer>
)。另一方面考余,AppCmp使用TodoAppCmp的標(biāo)簽來傳遞Footer先嬉,鑒于我們上面的術(shù)語(yǔ),Footer是Content Child,我們可以以下列方式訪問:
// ...@Component(...)class TodoAppCmp { @ContentChild(Footer) footer: Footer; ngAfterContentInit() { // this.footer is now with value set }}// ...

我們從上面可以看到,View Children和Content Children的區(qū)別是裝飾器的名字和生命周期不同楚堤,為了獲取所有的Children疫蔓,我們使用@ContentChildren
(如果只要獲取一個(gè),可以使用ContentChild)钾军,Children會(huì)在ngAfterContentInit
之后被設(shè)置
viewProviders vs providers
好吧鳄袍!我們差不多講完了,最后讓我們來看下providers和viewProviders之間的區(qū)別吏恭,如果你還不熟悉Angular 2的依賴注入拗小,可以看下該文章Angular 2中的依賴注入
讓我們來看下TodoAppCmp
class TodoList { private todos: Todo[] = []; add(todo: Todo) {} remove(todo: Todo) {} set(todo: Todo, index: number) {} get(index: number) {} getAll() {}}@Component({ // ... viewProviders: [TodoList], directives: [TodoCmp, TodoInputCmp], // ...})class TodoAppCmp { constructor(private todos: TodoList) {} // ...}

在@Component
我們?cè)O(shè)置了viewProviders
屬性樱哼,是一個(gè)數(shù)組哀九,里面有一個(gè)元素TodoList
服務(wù),TodoList
服務(wù)保存了所有todo項(xiàng)搅幅。
我們?cè)赥odoAppCmp構(gòu)造函數(shù)中注入了TodoList服務(wù)阅束,但我們也可以在TodoAppCmp視圖中使用的其他指令(或組件)中注入該服務(wù),這意味著下列組件可以使用TodoList:
TodoList
TodoCmp
TodoInputCmp

但是茄唐,如果我們嘗試在footer組件的構(gòu)造函數(shù)中注入該服務(wù)息裸,我們會(huì)得到以下運(yùn)行時(shí)錯(cuò)誤:
EXCEPTION: No provider for TodoList! (Footer -> TodoList)

這意味著在viewProviders
聲明的只允許組件的模板內(nèi)的組件使用。
如果我們想讓Footer組件使用TodoList服務(wù)沪编,可以將TodoList聲明在providers屬性中呼盆。
何時(shí)使用viewProviders
為什么要使用viewProviders?如果這些provider無法被content children訪問到?假設(shè)你現(xiàn)在在開發(fā)第三方庫(kù)蚁廓,它內(nèi)部使用一些服務(wù)访圃,這些服務(wù)是庫(kù)私有API的一部分,你不想讓它們能被外部訪問到相嵌,如果這些私有的內(nèi)容注冊(cè)在providers里用戶可以通過content children來訪問到這些私有的內(nèi)容腿时,但是如果你使用viewProviders况脆,該providers將無法從外部訪問

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市批糟,隨后出現(xiàn)的幾起案子格了,更是在濱河造成了極大的恐慌,老刑警劉巖跃赚,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笆搓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡纬傲,警方通過查閱死者的電腦和手機(jī)满败,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叹括,“玉大人算墨,你說我怎么就攤上這事≈祝” “怎么了净嘀?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)侠讯。 經(jīng)常有香客問我挖藏,道長(zhǎng),這世上最難降的妖魔是什么厢漩? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任膜眠,我火速辦了婚禮,結(jié)果婚禮上溜嗜,老公的妹妹穿的比我還像新娘宵膨。我一直安慰自己,他們只是感情好炸宵,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布辟躏。 她就那樣靜靜地躺著,像睡著了一般土全。 火紅的嫁衣襯著肌膚如雪捎琐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天裹匙,我揣著相機(jī)與錄音野哭,去河邊找鬼。 笑死幻件,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蛔溃。 我是一名探鬼主播绰沥,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼篱蝇,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了徽曲?” 一聲冷哼從身側(cè)響起零截,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秃臣,沒想到半個(gè)月后涧衙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奥此,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年弧哎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稚虎。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撤嫩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蠢终,到底是詐尸還是另有隱情序攘,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布寻拂,位于F島的核電站程奠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏祭钉。R本人自食惡果不足惜瞄沙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望朴皆。 院中可真熱鬧帕识,春花似錦、人聲如沸遂铡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扒接。三九已至伪货,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钾怔,已是汗流浹背碱呼。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宗侦,地道東北人愚臀。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像矾利,于是被迫代替她去往敵國(guó)和親姑裂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子馋袜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容