填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

內容投影

Content projection

本主題描述如何使用內容投影來建立靈活的可複用元件。

This topic describes how to use content projection to create flexible, reusable components.

要檢視或下載本主題中用到的示例程式碼,請參見現場演練 / 下載範例

To view or download the example code used in this topic, see the現場演練 / 下載範例.

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

Content projection is a pattern in which you insert, or project, the content you want to use inside another component. For example, you could have a Card component that accepts content provided by another component.

以下各節介紹了 Angular 中內容投影的常見實現,包括:

The following sections describe common implementations of content projection in Angular, including:

單插槽內容投影

Single-slot content projection

內容投影的最基本形式是單插槽內容投影。單插槽內容投影是指建立一個元件,你可以在其中投影一個元件。

The most basic form of content projection is single-slot content projection. Single-slot content projection refers to creating a component into which you can project one component.

要建立使用單插槽內容投影的元件,請執行以下操作:

To create a component that uses single-slot content projection:

  1. 建立一個元件。

    Create a component.

  2. 在元件範本中,新增 ng-content 元素,讓你希望投影的內容出現在其中。

    In the template for your component, add an ng-content element where you want the projected content to appear.

例如,以下元件使用 ng-content 元素來顯示訊息。

For example, the following component uses an ng-content element to display a message.

content-projection/src/app/zippy-basic/zippy-basic.component.ts
      
      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 元素,該元件的使用者現在可以將自己的訊息投影到該元件中。例如:

With the ng-content element in place, users of this component can now project their own message into the component. For example:

content-projection/src/app/app.component.html
      
      <app-zippy-basic>
  <p>Is content projection cool?</p>
</app-zippy-basic>
    

ng-content 元素是一個佔位符,它不會建立真正的 DOM 元素。ng-content 的那些自訂屬性將被忽略。

The ng-content element is a placeholder that does not create a real DOM element. Custom attributes applied to ng-content are ignored.

多插槽內容投影

Multi-slot content projection

一個元件可以具有多個插槽。每個插槽可以指定一個 CSS 選擇器,該選擇器會決定將哪些內容放入該插槽。該模式稱為多插槽內容投影。使用此模式,你必須指定希望投影內容出現在的位置。你可以透過使用 ng-contentselect 屬性來完成此任務。

A component can have multiple slots. Each slot can specify a CSS selector that determines which content goes into that slot. This pattern is referred to as multi-slot content projection. With this pattern, you must specify where you want the projected content to appear. You accomplish this task by using the select attribute of ng-content.

要建立使用多插槽內容投影的元件,請執行以下操作:

To create a component that uses multi-slot content projection:

  1. 建立一個元件。

    Create a component.

  2. 在元件範本中,新增 ng-content 元素,讓你希望投影的內容出現在其中。

    In the template for your component, add an ng-content element where you want the projected content to appear.

  3. select 屬性新增到 ng-content 元素。 Angular 使用的選擇器支援標籤名、屬性、CSS 類別和 :not 偽類別的任意組合。

    Add a select attribute to the ng-content elements. Angular supports selectors for any combination of tag name, attribute, CSS class, and the :not pseudo-class.

    例如,以下元件會使用兩個 ng-content 元素。

    For example, the following component uses two ng-content elements.

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

使用 question 屬性的內容將投影到帶有 select=[question] 屬性的 ng-content 元素。

Content that uses the question attribute is projected into the ng-content element with the select=[question] attribute.

content-projection/src/app/app.component.html
      
      <app-zippy-multislot>
  <p question>
    Is content projection cool?
  </p>
  <p>Let's learn about content projection!</p>
</app-zippy-multislot>
    
不帶 select 屬性的 ng-content
ng-content without a select attribute

如果你的元件包含不帶 select 屬性的 ng-content 元素,則該實例將接收所有與其他 ng-content 元素都不匹配的投影元件。

If your component includes an ng-content element without a select attribute, that instance receives all projected components that do not match any of the other ng-content elements.

在前面的示例中,只有第二個 ng-content 元素定義了 select 屬性。結果,第一個 ng-content 就會元素接收投影到元件中的任何其他內容。

In the preceding example, only the second ng-content element defines a select attribute. As a result, the first ng-content element receives any other content projected into the component.

有條件的內容投影

Conditional content projection

如果你的元件需要有條件地渲染內容或多次渲染內容,則應配置該元件以接受一個 ng-template 元素,其中包含要有條件渲染的內容。

If your component needs to conditionally render content, or render content multiple times, you should configure that component to accept an ng-template element that contains the content you want to conditionally render.

在這種情況下,不建議使用 ng-content 元素,因為只要元件的使用者提供了內容,即使該元件從未定義 ng-content 元素或該 ng-content 元素位於 ngIf 語句的內部,該內容也總會被初始化。

Using an ng-content element in these cases is not recommended, because when the consumer of a component supplies the content, that content is always initialized, even if the component does not define an ng-content element or if that ng-content element is inside of an ngIf statement.

使用 ng-template 元素,你可以讓元件根據你想要的任何條件顯式渲染內容,並可以進行多次渲染。在顯式渲染 ng-template 元素之前,Angular 不會初始化該元素的內容。

With an ng-template element, you can have your component explicitly render content based on any condition you want, as many times as you want. Angular will not initialize the content of an ng-template element until that element is explicitly rendered.

ng-template 進行條件內容投影的典型實現。

The following steps demonstrate a typical implementation of conditional content projection using ng-template.

  1. 建立一個元件。

    Create a component.

  2. 在接受 ng-template 元素的元件中,使用 ng-container 元素渲染該範本,例如:

    In the component that accepts an ng-template element, use an ng-container element to render that template, such as:

    content-projection/src/app/example-zippy.template.html
          
          <ng-container [ngTemplateOutlet]="content.templateRef"> </ng-container>
        

    本示例使用 ngTemplateOutlet 指令來渲染給定的 ng-template 元素,你將在後續步驟中對其進行定義。你可以將 ngTemplateOutlet 指令應用於任何型別的元素。本示例就將該指令分配給了 ng-container 元素,因為該元件不需要渲染真實的 DOM 元素。

    This example uses the ngTemplateOutlet directive to render a given ng-template element, which you will define in a later step. You can apply an ngTemplateOutlet directive to any type of element. This example assigns the directive to an ng-container element because the component does not need to render a real DOM element.

  3. ng-container 元素包裝在另一個元素(例如 div 元素)中,然後應用條件邏輯。

    Wrap the ng-container element in another element, such as a div element, and apply your conditional logic.

    content-projection/src/app/example-zippy.template.html
          
          <div *ngIf="expanded" [id]="contentId">
        <ng-container [ngTemplateOutlet]="content.templateRef"> </ng-container>
    </div>
        
  4. 在要投影內容的範本中,將投影的內容包裝在 ng-template 元素中,例如:

    In the template where you want to project content, wrap the projected content in an ng-template element, such as:

          
          <ng-template appExampleZippyContent>
      It depends on what you do with it.
    </ng-template>
        

    這個 ng-template 元素定義了一個元件可以根據其自身邏輯渲染的內容塊。元件可以使用 @ContentChild@ContentChildren裝飾器獲得對此範本內容的參考(即 TemplateRef)。前面的示例建立了一個自訂指令 appExampleZippyContent 作為 API,以將 ng-template 標記為元件內容。藉助這個 TemplateRef,元件可以使用 ngTemplateOutlet指令或ViewContainerRef.createEmbeddedView方法來渲染所參考的內容。

    The ng-template element defines a block of content that a component can render based on its own logic. A component can get a reference to this template content, or TemplateRef, by using either the @ContentChildor @ContentChildrendecorators. The preceding example creates a custom directive, appExampleZippyContent, as an API to mark the ng-template for the component's content. With the TemplateRef, the component can render the referenced content by using either the ngTemplateOutletdirective, or with ViewContainerRef.createEmbeddedView.

  5. 建立一個帶有與這個範本的自訂屬性相匹配的選擇器指令。在此指令中,注入 TemplateRef 實例。

    Create a directive with a selector that matches the custom attribute for your template. In this directive, inject a TemplateRef instance.

    content-projection/src/app/app.component.ts
          
          @Directive({
      selector: '[appExampleZippyContent]'
    })
    export class ZippyContentDirective {
      constructor(public templateRef: TemplateRef<unknown>) {}
    }
        

    在上一步中,你已添加了具有自訂屬性 appExampleZippyDirectiveng-template 元素。這段程式碼提供了當 Angular 遇到該自訂屬性時要使用的邏輯。在這裡,該邏輯指示 Angular 實例化這個範本參考。

    In the previous step, you added an ng-template element with a custom attribute, appExampleZippyDirective. This code provides the logic that Angular will use when it encounters that custom attribute. In this case, that logic instructs Angular to instantiate a template reference.

  6. 在你要將內容投影到的元件中,使用 @ContentChild 獲取此投影內容的範本。

    In the component you want to project content into, use @ContentChild to get the template of the projected content.

    content-projection/src/app/app.component.ts
          
          @ContentChild(ZippyContentDirective) content!: ZippyContentDirective;
        

    在執行此步驟之前,你的應用具有一個元件,它會在滿足某些條件時實例化此範本。你還建立了一個指令,該指令能提供對該範本的參考。在最後一步中,@ContentChild 裝飾器指示 Angular 實例化指定元件中的範本。

    Prior to this step, your application has a component that instantiates a template when certain conditions are met. You've also created a directive that provides a reference to that template. In this last step, the @ContentChild decorator instructs Angular to instantiate the template in the designated component.

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

    In the case of multi-slot content projection, you can use @ContentChildren to get a QueryList of projected elements.

在更復雜的環境中投影內容

Projecting content in more complex environments

多插槽內容投影中所述,你通常會使用屬性、元素、CSS 類別或這三者的某種組合來標識將內容投影到何處。例如,在以下 HTML 範本中,p 標籤會使用自訂屬性 question 將內容投影到 app-zippy-multislot 元件中。

As described in Multi-slot Content Projection, you typically use either an attribute, element, CSS Class, or some combination of all three to identify where to project your content. For example, in the following HTML template, a paragraph tag uses a custom attribute, question, to project content into the app-zippy-multislot component.

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

在某些情況下,你可能希望將內容投影為其他元素。例如,你要投影的內容可能是另一個元素的子元素。你可以用 ngProjectAs 屬性來完成此操作。

In some cases, you might want to project content as a different element. For example, the content you want to project might be a child of another element. You can accomplish this by using the ngProjectAs attribute.

例如,考慮以下 HTML 程式碼段:

For instance, consider the following HTML snippet:

content-projection/src/app/app.component.html
      
      <ng-container ngProjectAs="question">
  <p>Is content projection cool?</p>
</ng-container>
    

本示例使用 ng-container 屬性來模擬將元件投影到更復雜的結構中。

This example uses an ng-container attribute to simulate projecting a component into a more complex structure.

注意!
Reminder!

ng-container 元素是一個邏輯結構,可用於對其他 DOM 元素進行分組;但是,ng-container 本身未在 DOM 樹中渲染。

The ng-container element is a logical construct that you can use to group other DOM elements; however, the ng-container itself is not rendered in the DOM tree.

在這個例子中,我們要投影的內容位於另一個元素內。為了按預期方式投影此內容,此範本使用了 ngProjectAs 屬性。有了 ngProjectAs,就可以用 question 選擇器將整個 ng-container 元素投影到元件中。

In this example, the content we want to project resides inside another element. To project this content as intended, the template uses the ngProjectAs attribute. With ngProjectAs, the entire ng-container element is projected into a component using the question selector.