Перейти к содержанию

Проекция содержимого

В этой теме описывается, как использовать проекцию содержимого для создания гибких, многократно используемых компонентов.

Для просмотра или загрузки кода примера, использованного в этой теме, см. пример.

Проекция содержимого — это шаблон, в котором вы вставляете, или проектируете, содержимое, которое хотите использовать, внутрь другого компонента. Например, у вас может быть компонент Card, который принимает содержимое, предоставленное другим компонентом.

В следующих разделах описываются общие реализации проекции содержимого в Angular, включая:

Проекция контента Подробности
Проекция содержимого с одним слотом При этом типе проекции контента компонент принимает контент из одного источника.
Проекция содержимого с несколькими слотами В этом сценарии компонент принимает содержимое из нескольких источников.
Условная проекция контента Компоненты, использующие условную проекцию контента, отображают контент только при выполнении определенных условий.

Однослотовая проекция контента

Самой основной формой проекции контента является однослотовая проекция контента. Однослотовая проекция содержимого относится к созданию компонента, в который вы можете спроецировать один компонент.

Чтобы создать компонент, использующий однослотовую проекцию содержимого, выполните следующие действия:

  1. Создать компонент.

  2. В шаблоне компонента добавьте элемент <ng-content> в то место, где должно отображаться проецируемое содержимое.

Например, следующий компонент использует элемент <ng-content> для отображения сообщения.

content-projection/src/app/zippy-basic/zippy-basic.component.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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> пользователи этого компонента теперь могут проецировать свое собственное сообщение в компонент. Например:

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

Элемент <ng-content> — это заполнитель, который не создает реального элемента DOM. Пользовательские атрибуты, применяемые к <ng-content>, игнорируются.

Проекция содержимого на несколько слотов

Компонент может иметь несколько слотов. Каждый слот может определять CSS-селектор, который определяет, какое содержимое попадает в этот слот.

Этот шаблон называется проекция содержимого на несколько слотов.

При использовании этого шаблона вы должны указать, где должно отображаться проецируемое содержимое.

Для этого используется атрибут select в <ng-content>.

Чтобы создать компонент, использующий многослотовую проекцию содержимого, выполните следующие действия:

  1. Создать компонент.

  2. В шаблоне компонента добавьте элемент <ng-content>, в котором должно отображаться проецируемое содержимое.

  3. Добавьте атрибут select к элементам <ng-content>.

    Angular поддерживает selectors для любой комбинации имени тега, атрибута, CSS-класса и псевдокласса :not.

    Например, следующий компонент использует два элемента <ng-content>.

    content-projection/src/app/zippy-multislot/zippy-multislot.component.ts
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    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, проецируется в элемент <ng-content> с атрибутом select=[question].

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

ng-content без атрибута select

Если ваш компонент включает элемент <ng-content> без атрибута select, этот экземпляр получает все спроецированные компоненты, которые не соответствуют ни одному из других элементов <ng-content>.

В предыдущем примере только второй элемент <ng-content> определяет атрибут select. В результате первый элемент <ng-content> получает любое другое содержимое, проецируемое в компонент.

Условное проецирование содержимого

Если ваш компонент должен условно отображать содержимое или отображать его несколько раз, вы должны настроить этот компонент на прием элемента <ng-template>, который содержит содержимое, которое вы хотите условно отобразить.

Использование элемента <ng-content> в этих случаях не рекомендуется, потому что когда потребитель компонента предоставляет содержимое, это содержимое всегда инициализируется, даже если компонент не определяет элемент <ng-content> или если этот элемент <ng-content> находится внутри оператора ngIf.

С помощью элемента <ng-template> вы можете заставить свой компонент явно выводить содержимое на основе любого условия, сколько угодно раз. Angular не будет инициализировать содержимое элемента <ng-template> до тех пор, пока этот элемент не будет явно отрисован.

Следующие шаги демонстрируют типичную реализацию условного проецирования содержимого с использованием <ng-template>.

  1. Создайте компонент.

  2. В компоненте, который принимает элемент <ng-template>, используйте элемент <ng-container> для визуализации этого шаблона, например:

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

    Этот пример использует директиву ngTemplateOutlet для рендеринга заданного элемента <ng-template>, который вы определите на следующем этапе. Вы можете применить директиву ngTemplateOutlet к любому типу элемента.

    В данном примере директива назначается элементу <ng-container>, поскольку компоненту не нужно отображать реальный элемент DOM.

  3. Заверните элемент <ng-container> в другой элемент, например, в элемент div, и примените свою условную логику.

    1
    2
    3
    4
    5
    <div *ngIf="expanded" [id]="contentId">
        <ng-container
            [ngTemplateOutlet]="content.templateRef"
        ></ng-container>
    </div>
    
  4. В шаблоне, в который вы хотите спроецировать содержимое, оберните спроецированное содержимое в элемент <ng-template>, например:

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

    Элемент <ng-template> определяет блок содержимого, который компонент может отобразить на основе своей собственной логики. Компонент может получить ссылку на содержимое шаблона, или TemplateRef, используя декораторы @ContentChild или @ContentChildren. В предыдущем примере создается пользовательская директива appExampleZippyContent в качестве API для обозначения <ng-template> для содержимого компонента. С помощью TemplateRef компонент может выводить содержимое, на которое ссылается, используя либо директиву ngTemplateOutlet, либо метод ViewContainerRef createEmbeddedView().

  5. Создайте директиву атрибутов с селектором, который соответствует пользовательскому атрибуту для вашего шаблона. В эту директиву введите экземпляр TemplateRef.

    content-projection/src/app/example-zippy.component.ts
    1
    2
    3
    4
    5
    6
    7
    8
    @Directive({
        selector: '[appExampleZippyContent]',
    })
    export class ZippyContentDirective {
        constructor(
            public templateRef: TemplateRef<unknown>
        ) {}
    }
    

    В предыдущем шаге вы добавили элемент <ng-template> с пользовательским атрибутом appExampleZippyContent. Этот код предоставляет логику, которую Angular будет использовать, когда встретит этот пользовательский атрибут. В данном случае эта логика предписывает Angular создать ссылку на шаблон.

  6. В компоненте, на который вы хотите спроецировать содержимое, используйте @ContentChild для получения шаблона проецируемого содержимого.

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

    До этого шага в вашем приложении есть компонент, который создает шаблон при выполнении определенных условий. Вы также создали директиву, которая предоставляет ссылку на этот шаблон. На этом последнем шаге декоратор @ContentChild инструктирует Angular инстанцировать шаблон в указанном компоненте.

    В случае многослотовой проекции содержимого используйте @ContentChildren, чтобы получить QueryList проецируемых элементов.

Проецирование содержимого в более сложных средах

Как описано в "Многослотовое проецирование содержимого", вы обычно используете либо атрибут, либо элемент, либо CSS-класс, либо некоторую комбинацию всех трех для определения места проецирования содержимого. Например, в следующем шаблоне HTML тег абзаца использует пользовательский атрибут question для проецирования содержимого в компонент app-zippy-multislot.

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

В некоторых случаях вы можете захотеть спроецировать содержимое как другой элемент. Например, содержимое, которое вы хотите спроецировать, может быть дочерним элементом другого элемента.

Для этого используется атрибут ngProjectAs.

Например, рассмотрим следующий фрагмент HTML:

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

В этом примере используется атрибут <ng-container> для имитации проецирования компонента в более сложную структуру.

Напоминание

Элемент ng-container — это логическая конструкция, которая используется для группировки других элементов DOM; однако сам ng-container не отображается в дереве DOM.

В данном примере содержимое, которое мы хотим спроецировать, находится внутри другого элемента. Чтобы спроецировать это содержимое, шаблон использует атрибут ngProjectAs. С помощью ngProjectAs весь элемент <ng-container> проецируется в компонент с помощью селектора [question].

📅 28.02.2022

Комментарии