Angular 內(nèi)容投影

2022-06-29 11:10 更新

內(nèi)容投影

本主題描述如何使用內(nèi)容投影來創(chuàng)建靈活的可復(fù)用組件。

要查看或下載本主題中用到的示例代碼,請(qǐng)參見現(xiàn)場(chǎng)演練 / 下載范例 。

內(nèi)容投影是一種模式,你可以在其中插入或投影要在另一個(gè)組件中使用的內(nèi)容。例如,你可能有一個(gè) ?Card ?組件,它可以接受另一個(gè)組件提供的內(nèi)容。

以下各節(jié)介紹了 Angular 中內(nèi)容投影的常見實(shí)現(xiàn),包括:

  • 單插槽內(nèi)容投影。使用這種類型的內(nèi)容投影,組件可以從單一來源接受內(nèi)容。
  • 多插槽內(nèi)容投影。在這種情況下,組件可以從多個(gè)來源接受內(nèi)容。
  • 有條件的內(nèi)容投影。使用條件內(nèi)容投影的組件僅在滿足特定條件時(shí)才渲染內(nèi)容。

單插槽內(nèi)容投影

內(nèi)容投影的最基本形式是單插槽內(nèi)容投影。單插槽內(nèi)容投影是指創(chuàng)建一個(gè)組件,你可以在其中投影一個(gè)組件。

要?jiǎng)?chuàng)建使用單插槽內(nèi)容投影的組件,請(qǐng)執(zhí)行以下操作:

  1. 創(chuàng)建一個(gè)組件。
  2. 在組件模板中,添加 ?<ng-content>? 元素,讓你希望投影的內(nèi)容出現(xiàn)在其中。

例如,以下組件使用 ?<ng-content>? 元素來顯示消息。

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

@Component({
  selector: 'app-zippy-basic',
  template: `
    <h2>Single-slot content projection</h2>
    <ng-content></ng-content>
  `
})
export class ZippyBasicComponent {}

有了 ?<ng-content>? 元素,該組件的用戶現(xiàn)在可以將自己的消息投影到該組件中。例如:

<app-zippy-basic>
  <p>Is content projection cool?</p>
</app-zippy-basic>

?<ng-content>? 元素是一個(gè)占位符,它不會(huì)創(chuàng)建真正的 DOM 元素。?<ng-content>? 的那些自定義屬性將被忽略。

多插槽內(nèi)容投影

一個(gè)組件可以具有多個(gè)插槽。每個(gè)插槽可以指定一個(gè) CSS 選擇器,該選擇器會(huì)決定將哪些內(nèi)容放入該插槽。該模式稱為多插槽內(nèi)容投影。使用此模式,你必須指定希望投影內(nèi)容出現(xiàn)在的位置。你可以通過使用 ?<ng-content>? 的 ?select ?屬性來完成此任務(wù)。

要?jiǎng)?chuàng)建使用多插槽內(nèi)容投影的組件,請(qǐng)執(zhí)行以下操作:

  1. 創(chuàng)建一個(gè)組件。
  2. 在組件模板中,添加 ?<ng-content>? 元素,讓你希望投影的內(nèi)容出現(xiàn)在其中。
  3. 將 ?select ?屬性添加到 ?<ng-content>? 元素。 Angular 使用的選擇器支持標(biāo)簽名、屬性、CSS 類和? :not? 偽類的任意組合。
  4. 例如,以下組件會(huì)使用兩個(gè) ?<ng-content>? 元素。

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-zippy-multislot',
      template: `
        <h2>Multi-slot content projection</h2>
    
        Default:
        <ng-content></ng-content>
    
        Question:
        <ng-content select="[question]"></ng-content>
      `
    })
    export class ZippyMultislotComponent {}

使用 ?question ?屬性的內(nèi)容將投影到帶有 ?select=[question]? 屬性的 ?<ng-content>? 元素。

<app-zippy-multislot>
  <p question>
    Is content projection cool?
  </p>
  <p>Let's learn about content projection!</p>
</app-zippy-multislot>
不帶 SELECT 屬性的 NG-CONTENT
如果你的組件包含不帶 ?select ?屬性的 ?<ng-content>? 元素,則該實(shí)例將接收所有與其他 ?<ng-content>? 元素都不匹配的投影組件。
在前面的示例中,只有第二個(gè) ?<ng-content>? 元素定義了 ?select ?屬性。結(jié)果,第一個(gè) ?<ng-content>? 就會(huì)元素接收投影到組件中的任何其他內(nèi)容。

有條件的內(nèi)容投影

如果你的組件需要有條件地渲染內(nèi)容或多次渲染內(nèi)容,則應(yīng)配置該組件以接受一個(gè) ?<ng-template>? 元素,其中包含要有條件渲染的內(nèi)容。

在這種情況下,不建議使用 ?<ng-content>? 元素,因?yàn)橹灰M件的使用者提供了內(nèi)容,即使該組件從未定義 ?<ng-content>? 元素或該 ?<ng-content>? 元素位于 ?ngIf ?語句的內(nèi)部,該內(nèi)容也總會(huì)被初始化。

使用 ?<ng-template>? 元素,你可以讓組件根據(jù)你想要的任何條件顯式渲染內(nèi)容,并可以進(jìn)行多次渲染。在顯式渲染 ?<ng-template>? 元素之前,Angular 不會(huì)初始化該元素的內(nèi)容。

?<ng-template>? 進(jìn)行條件內(nèi)容投影的典型實(shí)現(xiàn)。

  1. 創(chuàng)建一個(gè)組件。
  2. 在接受 ?<ng-template>? 元素的組件中,使用 ?<ng-container>? 元素渲染該模板,例如:
  3. <ng-container [ngTemplateOutlet]="content.templateRef"></ng-container>

    本示例使用 ?ngTemplateOutlet ?指令來渲染給定的 ?<ng-template>? 元素,你將在后續(xù)步驟中對(duì)其進(jìn)行定義。你可以將 ?ngTemplateOutlet ?指令應(yīng)用于任何類型的元素。本示例就將該指令分配給了 ?<ng-container>? 元素,因?yàn)樵摻M件不需要渲染真實(shí)的 DOM 元素。

  4. 將 ?<ng-container>? 元素包裝在另一個(gè)元素(例如 ?div ?元素)中,然后應(yīng)用條件邏輯。
  5. <div *ngIf="expanded" [id]="contentId">
        <ng-container [ngTemplateOutlet]="content.templateRef"></ng-container>
    </div>
  6. 在要投影內(nèi)容的模板中,將投影的內(nèi)容包裝在 ?<ng-template>? 元素中,例如:
  7. <ng-template appExampleZippyContent>
      It depends on what you do with it.
    </ng-template>

    這個(gè) ?<ng-template>? 元素定義了一個(gè)組件可以根據(jù)其自身邏輯渲染的內(nèi)容塊。組件可以使用 ?@ContentChild? 或 ?@ContentChildren? 裝飾器獲得對(duì)此模板內(nèi)容的引用(即 ?TemplateRef?)。前面的示例創(chuàng)建了一個(gè)自定義指令 ?appExampleZippyContent ?作為 API,以將 ?<ng-template>? 標(biāo)記為組件內(nèi)容。借助這個(gè) ?TemplateRef?,組件可以使用 ?ngTemplateOutlet?指令或?ViewContainerRef.createEmbeddedView()?方法來渲染所引用的內(nèi)容。

  8. 創(chuàng)建一個(gè)屬性型指令,它具有與這個(gè)模板的自定義屬性相匹配的選擇器。在此指令中,注入 TemplateRef 實(shí)例。
  9. @Directive({
      selector: '[appExampleZippyContent]'
    })
    export class ZippyContentDirective {
      constructor(public templateRef: TemplateRef<unknown>) {}
    }

    在上一步中,你已添加了具有自定義屬性 ?appExampleZippyDirective ?的 ?<ng-template>? 元素。這段代碼提供了當(dāng) Angular 遇到該自定義屬性時(shí)要使用的邏輯。在這里,該邏輯指示 Angular 實(shí)例化這個(gè)模板引用。

  10. 在你要將內(nèi)容投影到的組件中,使用 ?@ContentChild? 獲取此投影內(nèi)容的模板。
  11. @ContentChild(ZippyContentDirective) content!: ZippyContentDirective;

    在執(zhí)行此步驟之前,你的應(yīng)用具有一個(gè)組件,它會(huì)在滿足某些條件時(shí)實(shí)例化此模板。你還創(chuàng)建了一個(gè)指令,該指令能提供對(duì)該模板的引用。在最后一步中,?@ContentChild? 裝飾器指示 Angular 實(shí)例化指定組件中的模板。

    如果是多插槽內(nèi)容投影,則可以使用 ?@ContentChildren? 獲取投影元素的查詢列表(QueryList)。

在更復(fù)雜的環(huán)境中投影內(nèi)容

如多插槽內(nèi)容投影中所述,你通常會(huì)使用屬性、元素、CSS 類或這三者的某種組合來標(biāo)識(shí)將內(nèi)容投影到何處。例如,在以下 HTML 模板中,p 標(biāo)簽會(huì)使用自定義屬性 ?question ?將內(nèi)容投影到 ?app-zippy-multislot? 組件中。

<app-zippy-multislot>
  <p question>
    Is content projection cool?
  </p>
  <p>Let's learn about content projection!</p>
</app-zippy-multislot>

在某些情況下,你可能希望將內(nèi)容投影為其他元素。例如,你要投影的內(nèi)容可能是另一個(gè)元素的子元素??梢杂?nbsp;?ngProjectAs ?屬性來完成此操作。

例如,考慮以下 HTML 代碼段:

<ng-container ngProjectAs="[question]">
  <p>Is content projection cool?</p>
</ng-container>

本示例使用 ?<ng-container>? 屬性來模擬將組件投影到更復(fù)雜的結(jié)構(gòu)中。

注意!
?<ng-container>? 元素是一個(gè)邏輯結(jié)構(gòu),可用于對(duì)其他 DOM 元素進(jìn)行分組;但是,?<ng-container>? 本身未在 DOM 樹中渲染。

在這個(gè)例子中,我們要投影的內(nèi)容位于另一個(gè)元素內(nèi)。為了按預(yù)期方式投影此內(nèi)容,此模板使用了 ?ngProjectAs ?屬性。有了 ?ngProjectAs?,就可以用 ?[question]? 選擇器將整個(gè) ?<ng-container>? 元素投影到組件中。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)