Проекция содержимого в Angular
Проекция содержимого — мощный инструмент в Angular, позволяющий разработчикам создавать гибкие и многократно используемые компоненты. Такой подход повышает возможность повторного использования и гибкость, что приводит к созданию более чистых и удобных в обслуживании приложений Angular.
В этой статье мы рассмотрим, что такое проецирование контента, его типы, как его использовать, а также приведем реальный сценарий проецирования контента с несколькими слотами.
Что такое проекция контента
Проекция контента — это шаблон, позволяющий вставлять или проецировать контент из родительского компонента в дочерние. Этот подход позволяет создавать гибкие компоненты, способные содержать различный контент, что делает их более пригодными для повторного использования.
Типы проекций контента
Проекция содержимого с одним слотом использует одну директиву <ng-content>
, позволяющую вставлять содержимое в определенное место.
Многослотовая проекция контента позволяет проецировать различные разделы контента в разные части шаблона. Проекция контента с несколькими слотами использует атрибут select
(<ng-content select="[card-subtitle]"></ng-content>
), чтобы указать, какой контент должен быть размещен именно там.
Несколько ключевых понятий, на которые следует обратить внимание:
<ng-content>
в шаблоне компонента определяет области-заполнители. Когда родительский компонент использует этот компонент, он передает содержимое между тегами, настраивая поведение дочернего компонента.<ng-container>
— это директива, позволяющая группировать элементы в шаблоне, который не влияет на стили или макет и не отображается в Angular DOM.<ng-template>
— это элемент шаблона, который Angular использует со структурными директивами. Эти элементы шаблона работают при наличии структурных директив и помогают нам определить шаблон, который сам по себе ничего не отображает, но условно отображает их в DOM.
Пример проецирования содержимого в один слот
Шаг 1: Создайте дочерний/повторно используемый компонент
@Component({
selector: 'app-single-slot-projection',
template: `<mat-toolbar>
<ng-content> </ng-content>
</mat-toolbar>`,
styles: ``
})
export class SingleSlotProjectionComponent {
}
Шаг 2: Используйте компонент с одним слотом в родительском компоненте
Все содержимое внутри <app-single-slot-projection>
будет проецироваться на место <ng-content>
дочернего компонента.
@Component({
selector: 'app-root',
template: `
<app-single-slot-projection>
<span>Content Projection Example</span>
</app-single-slot-projection>
`,
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'angular-multi-select-picker';
}
Пример многослотового отображения содержимого
Давайте представим себе приложение для создания динамических отчетов, работающее в реальном времени. Представьте, что вы работаете над таким приложением, которое позволяет пользователю выбирать необходимые поля для включения в отчет.
Для выбора полей используется экран, подобный тому, который мы видим на рисунке: слева отображаются названия всех доступных столбцов, а справа - названия выбранных столбцов. Действия расположены посередине. Пользователь может выбрать столбцы из левого меню и переместить их вправо, а также переместить их обратно.
Как часть основной команды, мы решили создать стандартные разделы в отчете, а пользователь может решить, что именно должно быть в каждом из этих разделов.
В ближайшем будущем мы опубликуем отдельное сообщение о том, как сделать этот компонент более повторно используемым, используя условные проекции.
Шаг 1: Создайте дочерний/повторно используемый компонент.
В рамках этого примера мы рассматриваем 5 мест для размещения контента в компоненте.
<mat-card appearance="outlined">
<mat-card-header class="mb-2">
<mat-card-title>
<ng-content select="[card-title]"></ng-content>
</mat-card-title>
<mat-card-subtitle class="mt-2">
<ng-content select="[card-subtitle]"></ng-content>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div class="list_builder">
<ng-content select="[source-items]"></ng-content>
<ng-content select="[action-items]"> </ng-content>
<ng-content select="[target-items]"></ng-content>
</div>
</mat-card-content>
</mat-card>
Шаг 2: Используем компонент с несколькими слотами в родительском компоненте
card-title
: отображает статический заголовок.card-subtitle
: отображает статическое подзаголовок, включающее количество доступных столбцов в правой боковой таблице.source-items
отображает все доступные столбцы, удовлетворяющие нашим требованиям.action-items
: отображает все доступные действия, которые можно выполнять с левой и правой таблицами.target-items
: отображает все выбранные столбцы для отображения в отчете.
<main class="main">
<div class="content">
<!-- Single Slot Projection -->
<app-single-slot-projection>
<span>Content Projection Example</span>
</app-single-slot-projection>
<!-- Multi Slot Projection -->
<app-multi-select-picker>
<!-- card-title section -->
<ng-container card-title>Multi Select Picker</ng-container>
<!-- card-subtitle section -->
<ng-container card-subtitle>Selected Items Count: {{targetItems.length}}</ng-container>
<!-- source-items section -->
<ng-container source-items>
<mat-card class="source_items">
<mat-card-content>
<mat-list role="list">
@for (data of sourceItems; track $index) {
<mat-list-item role="listitem" class="selectable_item" [ngClass]="{selected_item: data.selected}"
(click)="itemSelected(data)">
{{data.value}}
</mat-list-item>
}
</mat-list>
</mat-card-content>
</mat-card>
</ng-container>
<!-- action-items section -->
<ng-container action-items>
<mat-card class="action_items">
<mat-card-content class="action_buttons_container">
<button mat-icon-button class="action_buttons" (click)="moveAll('right')">
<mat-icon aria-hidden="false" aria-label="Move All Right"
fontIcon="keyboard_double_arrow_right"></mat-icon>
</button>
<button mat-icon-button class="action_buttons" (click)="moveSelected('right')">
<mat-icon aria-hidden="false" aria-label="Move Right" fontIcon="chevron_right"></mat-icon>
</button>
<button mat-icon-button class="action_buttons" (click)="moveSelected('left')">
<mat-icon aria-hidden="false" aria-label="Move Left" fontIcon="chevron_left"></mat-icon>
</button>
<button mat-icon-button class="action_buttons" (click)="moveAll('left')">
<mat-icon aria-hidden="false" aria-label="Move All Left" fontIcon="keyboard_double_arrow_left"></mat-icon>
</button>
</mat-card-content>
</mat-card>
</ng-container>
<!-- target-items section -->
<ng-container target-items>
<mat-card class="target_items">
<mat-card-content>
<mat-list role="list">
@for (data of targetItems; track $index) {
<mat-list-item role="listitem" class="selectable_item" [ngClass]="{selected_item: data.selected}"
(click)="itemSelected(data)">
{{data.value}}
</mat-list-item>
}
</mat-list>
</mat-card-content>
</mat-card>
</ng-container>
</app-multi-select-picker>
</div>
</main>
Рекомендации
- Применяйте согласованные правила наименования для выбранных атрибутов.
ngProjectAs
работает только со статическими значениями, а не с динамическими выражениями.- Важно избегать чрезмерного усложнения компонента с большим количеством слотов.
Вы можете найти код на GitHub: https://github.com/dayanandaeswar/angular-multi-select-picker/tree/using-content-projectionю