Angular: ng-template, ng-content, ng-container, *ngTemplateOutlet

Имеем родительский компонент app и дочерний child-component.

ng-content

1. Простое прокидывание контента из родителя в чайлд-компонент

Чтобы просто прокинуть контент из родителя его чайлду, то в родительском шаблоне (app.component.html) объявляем дочерний компонент (app-child-component) и внутри его тегов пишем то, что нужно прокинуть:

app.component.html:

<app-child-component>
 
  <h3 class="header">Header</h3>

  <p class="body">Body</p>

  <footer class="footer">Footer</footer>

</app-child-component>

В темплейте дочерного компонента просто ставим тег ng-content и в этом месте будет выведен контент, прокинутый из родителя:

child-component.component.html:

<ng-content></ng-content>

2. Прокидывание контент, используя его теги в качестве селекторов, для вывода их в разных местах

Допустим, прокинутый контент нам надо вывести не весь в одном месте, а разбить его на части и отобразить эти части в разных местах темплейта дочерного компонента. Для этого у тегов ng-content используется атрибут select. Это обычный css селектор, который дает возможность взять часть контента из ng-conent и вывести ее в нужном месте, а из других мест эта часть удалится. Выглядеть теперь темплейт дочерного компонента будет, например, так:

app.component.html:

<ng-content select=".header"></ng-content>

<p>child-component works!</p>

<ng-content></ng-content>

<hr />

<ng-content select="footer"></ng-content>

Выглядеть такой вывод будет так:

ng-content selector

ng-template и *ngTemplateOutlet

1. Простая отрисовка темлейта (ng-template) по его ссылке в нужном месте при помощи директивы *ngTemplateOutlet

Мы можем в любом месте шаблона компоненты вывести то, что заключено в теги <ng-template>. Для этого ng-template даем шаблонную ссылку (а нашем примере #ref) и используем для вывода в нужном месте ng-container с директивой *ngTemplateOutlet, передав этой директиве название ссылки.

<ng-content select=".header"></ng-content>

<p>child-component works!</p>

<ng-content></ng-content>

<hr />

<ng-container *ngTemplateOutlet="ref"></ng-container>

<ng-content select="footer"></ng-content>



<ng-template #ref>Some template</ng-template>

2. Передача контекста в ng-template из ng-container (из места вывода) по ключу

Допустим в нашем темплейте (внутри ng-template) какие-то данные могут меняться, зависимо от места, где мы при помощи ng-container его выводим. Эти как бы динамические данные мы можем прокидывать прямо в месте вывода, передавая контекст в наш ng-template.

Предположим, мы хочем прокинуть в качестве контекста в темплей цифры 123. Для этого вторым параметром директиве *ngTemplateOutlet передаем объект context, который содержит наши цифры 123 по ключу foo. А в ng-template нам теперь надо указать переменную (в нашем примере let-bar) и передать в нее название ключа контекста (foo), которое будем выводить внутри этого темплейта. Теперь эту переменную можно использовать внутри темплейта в фигурных скобках.

<hr />

<ng-container *ngTemplateOutlet="ref; context: { foo: 123 }"></ng-container>

<p>Some text</p>

<ng-template #ref let-bar="foo">Some template {{ bar }}</ng-template>

2. Передача контекста в ng-template из ng-container (из места вывода) с динамическим ключом

Проблема способа выше в том, что нужно знать название ключа в контексте (foo), чтобы передать его в переменную для вывода. Но есть способ обойти это и получать содержимое контекста по любой переменной без ключа.

Для этого в контекст передаем не обычное название ключа, а $implicit. И теперь в ng-template мы не присваиваем никакого значения переменной, а просто объявляем любую. При этом можно параллельно передавать и именные переменные в контекст, как и раньше:

<hr />

<ng-container *ngTemplateOutlet="ref; context: { foo: 123, $implicit: 'Hello' }"></ng-container>

<p>Some text</p>

<ng-template #ref let-bar="foo" let-test>
  {{ test }} Some template {{ bar }} 
</ng-template>

3. Передача ng-template от родителя с использованием контекста внутри дочерного компонента

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

В примере ниже используем просто цикл, чтобы не создавать таблицу. Он также не дает возможности воспользоваться просто ng-content, и нам уже нужен ng-template.

В шаблоне дочерного компонента циклом выводим темплейты и в качестве контекста передаем им под ключом $implicit элементы из перебираемого массива. Но при этом  первым значением, передаваемым в *ngTemplateOutlet идет переменная template – это TemplateRef приходящее через @Input. И вот только если template  от родителя не пришел, тогда при помощи оператора “ИЛИ” отрисовываем локальный темплейт.

child-component.component.html:

<ng-template #defaultTemplate let-element>Some template {{ element }}</ng-template>

<hr />

<div *ngFor="let item of [1, 2, 3]">
  <ng-container *ngTemplateOutlet="template || defaultTemplate; context: { $implicit: item }"></ng-container>
</div>

child-component.component.ts:

import { Component, Input, TemplateRef } from '@angular/core';

@Component({
  selector: 'app-child-component',
  templateUrl: './child-component.component.html',
  styleUrls: ['./child-component.component.scss'],
})
export class ChildComponentComponent {
  @Input() template?: TemplateRef<{ $implicit: number }>;
}

Теперь в родительском компоненте (app.component.html) создаем альтернативный ng-template, в котором указываем ссылку для контекста (в примере let-element, так как она может быть произвольной в данном случае благодаря $implicit). Затем присваиваем этому темплейте шаблонную ссылку (#ref) и передаем ее в качестве ипута в дочерный компонент.

app.component.html:

<app-child-component [template]="ref">
  <h3 class="header">Header</h3>

  <p class="body">Body</p>

  <footer class="footer">Footer</footer>
</app-child-component>

<ng-template let-element #ref>
  <p>{{ element }} I am from parent-component</p>
</ng-template>

В результате у нас теперь отрисовывается темплейт, переданный из родителя и использующий контекст, переданный в дочерном компоненте:

ng-template с родительского компонента

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *