angular-cn/aio/content/guide/content-projection.md

158 lines
9.8 KiB
Markdown

# Content projection
This topic describes how to use content projection to create flexible, reusable components.
<div class="alert is-helpful">
To view or download the example code used in this topic, see the <live-example></live-example>.
</div>
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.
The following sections describe common implementations of content projection in Angular, including:
* [Single-slot content projection](#single-slot). With this type of content projection, a component accepts content from a single source.
* [Multi-slot content projection](#multi-slot). In this scenario, a component accepts content from multiple sources.
* [Conditional content projection](#conditional). Components that use conditional content projection render content only when specific conditions are met.
{@a single-slot }
## 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](guide/component-overview) a component.
1. In the template for your component, add an `ng-content` element where you want the projected content to appear.
For example, the following component uses an `ng-content` element to display a message.
<code-example path="content-projection/src/app/zippy-basic/zippy-basic.component.ts" header="content-projection/src/app/zippy-basic/zippy-basic.component.ts"></code-example>
With the `ng-content` element in place, users of this component can now project their own message into the component. For example:
<code-example path="content-projection/src/app/app.component.html" header="content-projection/src/app/app.component.html"
region="single-slot"></code-example>
<div class="alert is-helpful">
The `ng-content` element is a placeholder that does not create a real DOM element. Custom attributes applied to `ng-content` are ignored.
</div>
{@a multi-slot}
## Multi-slot content projection
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](guide/component-overview) a component.
1. In the template for your component, add an `ng-content` element where you want the projected content to appear.
1. Add a `select` attribute to the `ng-content` elements. Angular supports [selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) for any combination of tag name, attribute, CSS class, and the `:not` pseudo-class.
For example, the following component uses two `ng-content` elements.
<code-example path="content-projection/src/app/zippy-multislot/zippy-multislot.component.ts" header="content-projection/src/app/zippy-multislot/zippy-multislot.component.ts"></code-example>
Content that uses the `question` attribute is projected into the `ng-content` element with the `select=[question]` attribute.
<code-example path="content-projection/src/app/app.component.html" header="content-projection/src/app/app.component.html"
region="multi-slot"></code-example>
<div class="callout is-helpful">
<header>ng-content without a select attribute</header>
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.
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.
</div>
{@a conditional }
## Conditional content projection
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.
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.
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.
The following steps demonstrate a typical implementation of conditional content projection using `ng-template`.
1. [Create](guide/component-overview) a component.
1. In the component that accepts an `ng-template` element, use an `ng-container` element to render that template, such as:
<code-example path="content-projection/src/app/example-zippy.template.html" header="content-projection/src/app/example-zippy.template.html" region="ng-container">
</code-example>
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.
1. Wrap the `ng-container` element in another element, such as a `div` element, and apply your conditional logic.
<code-example path="content-projection/src/app/example-zippy.template.html" header="content-projection/src/app/example-zippy.template.html" region="ngif">
</code-example>
1. In the template where you want to project content, wrap the projected content in an `ng-template` element, such as:
<code-example path="content-projection/src/app/app.component.html" region="ng-template">
</code-example>
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`](/api/core/TemplateRef), by using either the [`@ContentChild`](/api/core/ContentChild) or [`@ContentChildren`](/api/core/ContentChildren) decorators. 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 [`ngTemplateOutlet`](/api/common/NgTemplateOutlet) directive, or with [`ViewContainerRef.createEmbeddedView`](/api/core/ViewContainerRef#createembeddedview).
1. Create a directive with a selector that matches the custom attribute for your template. In this directive, inject a TemplateRef instance.
<code-example path="content-projection/src/app/app.component.ts" header="content-projection/src/app/app.component.ts" region="zippycontentdirective">
</code-example>
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.
1. In the component you want to project content into, use `@ContentChild` to get the template of the project content.
<code-example path="content-projection/src/app/app.component.ts" header="content-projection/src/app/app.component.ts" region="contentchild">
</code-example>
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.
<div class="alert is-helpful">
In the case of multi-slot content projection, you can use `@ContentChildren` to get a QueryList of projected elements.
</div>
{@a ngprojectas }
## Projecting content in more complex environments
As described in [Multi-slot Content Projection](#multi-slot), 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.
<code-example path="content-projection/src/app/app.component.html" header="content-projection/src/app/app.component.html"
region="multi-slot"></code-example>
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.
For instance, consider the following HTML snippet:
<code-example path="content-projection/src/app/app.component.html" header="content-projection/src/app/app.component.html" region="ngprojectas">
</code-example>
This example uses an `ng-container` attribute to simulate projecting a component into a more complex structure.
<div class="callout is-helpful">
<header>Reminder!</header>
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.
</div>
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.