Angular 2 ContentChild & ContentChildren

前面的文章咱們已經介紹過了 Angular 2 的 ViewChild & ViewChildren 屬性裝飾器,如今咱們就來介紹一下它們的兄弟 ContentChild 和 ContentChildren 屬性裝飾器。我想經過一個簡單的需求,來引入咱們今天的主題。具體需求以下:html

圖片描述

熟悉 Angular 1.x 的用戶,應該都知道 ng-transclude 指令,經過該指令咱們能夠很是容易實現上述的功能。而在 Angular 2 中引入新的 ng-content 指令,咱們立刻動手試一下。typescript

greet.component.tssegmentfault

import { Component } from '@angular/core';

@Component({
    selector: 'exe-greet',
    template: `
    <div class="border">
        <p>Greet Component</p>  
        <ng-content></ng-content>
    </div>
    `,
    styles: [` .border { border: 2px solid #eee; } `]
})
export class GreetComponent { }

app.component.ts瀏覽器

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h4>Welcome to Angular World</h4>
    <exe-greet>
      <p>Hello Semlinker</p>
    </exe-greet>
  `,
})
export class AppComponent { }

以上代碼運行後,瀏覽器的輸出結果:安全

圖片描述

是否是感受太簡單了,那咱們來升級一下需求,即咱們的 GreetComponent 組件要支持用戶自定義卡片風格的問候內容,具體以下圖所示:app

圖片描述

這個功能也很簡單啊,咱們立刻看一下代碼:this

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h4>Welcome to Angular World</h4>
    <exe-greet>
      <div style="border: 1px solid #666; margin: 4px;">
         <div style="border: 1px solid red; margin: 5px;">Card Header</div>
         <div style="border: 1px solid green; margin: 5px;">Card Body</div>
         <div style="border: 1px solid blue; margin: 5px;">Card Footer</div>
      </div>
    </exe-greet>
  `,
})
export class AppComponent { }

以上代碼運行後,瀏覽器的輸出結果:spa

圖片描述

功能是實現了,但有沒有什麼問題 ?假設在另外一個頁面,也須要使用 GreetComponent 組件 ,那麼還須要再次設置預設的樣式。咱們能不能把卡片風格的各個部分默認樣式定義在 GreetComponent 組件中,使用組件時咱們只需關心自定內容區域的樣式,答案是能夠的,調整後的代碼以下:3d

import { Component } from '@angular/core';

@Component({
    selector: 'exe-greet',
    template: `
    <div class="border">
        <p>Greet Component</p>  
        <div style="border: 1px solid #666;margin: 4px;">
            <div style="border: 1px solid red;margin: 5px;">
                <ng-content></ng-content>
            </div>
            <div style="border: 1px solid green;margin: 5px;">
                <ng-content></ng-content>
            </div>
            <div style="border: 1px solid blue;margin: 5px;">
                 <ng-content></ng-content>
            </div>
        </div>
    </div>
    `,
    styles: [` .border { border: 2px solid #eee; } `]
})
export class GreetComponent{ }

GreetComponent 組件已經調整好了,如今剩下的問題就是如何從父級組件動態的抽取各個部分的內容。幸運的是,ng-content 指令支持 select 屬性,它容許咱們設定抽取的內容,更強大的是它支持咱們經常使用的選擇器類型,如標籤選擇器、類選擇器、ID選擇器、屬性選擇器等。code

greet.component.ts - template

<div style="border: 1px solid #666;margin: 4px;">
     <div style="border: 1px solid red;margin: 5px;">
         <ng-content select="header"></ng-content>
     </div>
     <div style="border: 1px solid green;margin: 5px;">
         <ng-content select=".card_body"></ng-content>
     </div>
     <div style="border: 1px solid blue;margin: 5px;">
         <ng-content select="footer"></ng-content>
     </div>
</div>

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h4>Welcome to Angular World</h4>
    <exe-greet>
      <header>Card Header</header>
          <div class="card_body">Card Body</div>
      <footer>Card Footer</footer>
    </exe-greet>
  `,
})
export class AppComponent { }

以上代碼運行後,在瀏覽上咱們能夠看到預期的結果,接下來該咱們的主角 - ContentChild 出場了。

ContentChild

在正式介紹 ContentChild 屬性裝飾器前,咱們要先了解一下 Content Projection (內容投影)。

圖片描述

之前面的例子爲例,咱們在 AppComponent 組件中定義的內容,經過 ng-content 指令提供的選擇器,成功投射到 GreetComponent 組件中。瞭解完 Content Projection,接下來咱們來介紹 ContentChild。

ContentChild 是屬性裝飾器,用來從經過 Content Projection 方式設置的視圖中獲取匹配的元素。

@ContentChild 示例

child.component.ts

import { Component } from '@angular/core';

@Component({
    selector: 'exe-child',
    template: `
      <p>Child Component</p>  
    `
})
export class ChildComponent {
    name: string = 'child-component';
}

parent.component.ts

import { Component, ContentChild, AfterContentInit } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
    selector: 'exe-parent',
    template: `
      <p>Parent Component</p>  
      <ng-content></ng-content>
    `
})
export class ParentComponent implements AfterContentInit {
    @ContentChild(ChildComponent)
    childCmp: ChildComponent;

    ngAfterContentInit() {
        console.dir(this.childCmp);
    }
}

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h4>Welcome to Angular World</h4>
    <exe-parent>
      <exe-child></exe-child>
    </exe-parent>
  `,
})
export class AppComponent { }

以上代碼運行後,控制檯的輸出結果:

圖片描述

ContentChildren

ContentChildren 屬性裝飾器用來從經過 Content Projection 方式設置的視圖中獲取匹配的多個元素,返回的結果是一個 QueryList 集合。

@ContentChildren 示例

parent.component.ts

import { Component, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
    selector: 'exe-parent',
    template: `
      <p>Parent Component</p>  
      <ng-content></ng-content>
    `
})
export class ParentComponent implements AfterContentInit {
    
    @ContentChildren(ChildComponent)
    childCmps: QueryList<ChildComponent>;

    ngAfterContentInit() {
        console.dir(this.childCmps);
    }
}

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h4>Welcome to Angular World</h4>
    <exe-parent>
      <exe-child></exe-child>
      <exe-child></exe-child>
    </exe-parent>
  `,
})
export class AppComponent { }

以上代碼運行後,控制檯的輸出結果:

圖片描述

ContentChild 接口及裝飾器

ContentChildDecorator 接口

export interface ContentChildDecorator {
  // Type類型:@ContentChild(ChildComponent)
  (selector: Type<any>|Function|string, {read}?: {read?: any}): any;

  new (selector: Type<any>|Function|string, {read}?: {read?: any}): ContentChild;
}

ContentChildDecorator 裝飾器

export const ContentChild: ContentChildDecorator = makePropDecorator(
    'ContentChild',
    [
      ['selector', undefined], {
        first: true,
        isViewQuery: false, // ViewChild或ViewChildren裝飾器時爲true
        descendants: true,
        read: undefined,
      }
    ],
Query);

我有話說

ContentChildren 與 ViewChildren 的定義

  • 在 host 元素 <opening></closing> 標籤中被稱爲 Content Children

  • 在組件的模板中定義的內容,它是組件的一部分,被稱爲 View Children

ContentChild 與 ViewChild 的異同點

相同點

  • 都是屬性裝飾器

  • 都有對應的複數形式裝飾器:ContentChildren、ViewChildren

  • 都支持 Type<any>|Function|string 類型的選擇器

不一樣點

  • ContentChild 用來從經過 Content Projection 方式 (ng-content) 設置的視圖中獲取匹配的元素

  • ViewChild 用來從模板視圖中獲取匹配的元素

  • 在父組件的 ngAfterContentInit 生命週期鉤子中才能成功獲取經過 ContentChild 查詢的元素

  • 在父組件的 ngAfterViewInit 生命週期鉤子中才能成功獲取經過 ViewChild 查詢的元素

在 Root Component 中沒法使用 `ng-content

圖片描述

緣由主要有如下幾點:

  • <my-app></my-app> 標籤之間的信息是用來代表 Angular 的應用程序正在啓動中

  • Angular 2 編譯器不會處理 index.html 文件中設置的綁定信息,另外出於安全因素考慮,爲了不 index.html 中的 {{}} 插值,被服務端使用的模板引擎處理。

總結

本文先經過一個簡單的需求,引出了 Angular 2 中的 ng-content 指令,進而介紹了 Angular Content Projection (內容投影) 的概念,最後才正式介紹 ContentChild 和 ContentChildren 屬性裝飾器。另外在文末咱們也介紹了 ContentChild 與 ViewChild 的異同點,但願本文能幫助讀者更好地理解相關的知識點。

相關文章
相關標籤/搜索