DevGang
Авторизоваться

Проекция содержимого в 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>

Рекомендации

  1. Применяйте согласованные правила наименования для выбранных атрибутов.
  2. ngProjectAs работает только со статическими значениями, а не с динамическими выражениями.
  3. Важно избегать чрезмерного усложнения компонента с большим количеством слотов.

Вы можете найти код на GitHub: https://github.com/dayanandaeswar/angular-multi-select-picker/tree/using-content-projectionю

Источник:

#Angular
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу