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-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>
В результате у нас теперь отрисовывается темплейт, переданный из родителя и использующий контекст, переданный в дочерном компоненте: