Директивы ng-template
и связанная ngTemplateOutlet
очень мощные инструменты Angular, которые часто используются с ng-container
.
ng-template
директива 'отрисовывает' Angular шаблон: это означает, что содержимое этого тега будет содержать часть шаблона, которая затем может быть использована вместе с другими шаблонами для формирования окончательного шаблона компонента.
Директива ng-template
используется под капотом в ngIf
, ngFor
и ngSwitch
директивах.
Давайте начнем изучение ng-template
на примере. Определим два таба (кнопки) компонента таба (больше - дальше).
@Component({
selector: 'app-root',
template: `
<ng-template>
<button class="tab-button"
(click)="login()">{{loginText}}</button>
<button class="tab-button"
(click)="signUp()">{{signUpText}}</button>
</ng-template>
`})
export class AppComponent {
loginText = 'Login';
signUpText = 'Sign Up';
lessons = ['Lesson 1', 'Lessons 2'];
login() {
console.log('Login');
}
signUp() {
console.log('Sign Up');
}
}
ng-template
ничего не рендерит, мы просто определяем шаблон, но пока не используем его.Возможно, вы впервые столкнулись с реализацией ng-template
в сценарии if/else
, например:
<div class="lessons-list" *ngIf="lessons else loading">
...
</div>
<ng-template #loading>
<div>Loading...</div>
</ng-template>
Очень частый случай: мы показываем альтернативный шаблон loading
пока данные не будут получены с бэка. Как вы видите условие else
указывает на шаблон, который имеет имя loading
. Имя привязано через переменную шаблона #loading
.
Помимо шаблона для else
, использование ngIf
также неявно создает второй ng-template
! Давайте взглянем на то что происходит под капотом:
<ng-template [ngIf]="lessons" [ngIfElse]="loading">
<div class="lessons-list">
...
</div>
</ng-template>
<ng-template #loading>
<div>Loading...</div>
</ng-template>
*ngIf
имеет более лаконичный синтаксис. Что же происходит под капотом *ngIf
:
ng-template
*ngIf
было разделено на две отдельные директивы [ngIf]
и [ngIfElse]
с использованием Input
синтаксисаngFor
и ngSwitch
работают схожим образом.
Отметьте, что мы не можем использовать несколько структурных директив на одном элементе.
Директива ng-container
позволяет применить структурную директиву к разделу страницы, не создавай при этом дополнительный элемент (тег).
Итак, чтобы не создавать дополнительный div
мы можем воспользоваться директивой ng-container
(в разметке тега ng-container
вы не увидите) и уже на ней применить структурную директиву:
<ng-container *ngIf="lessons">
<div class="lesson" *ngFor="let lesson of lessons">
<div class="lesson-detail">
{{lesson | json}}
</div>
</div>
</ng-container>
Есть еще важная черта директивы ng-container
: она может предоставить заполнитель для инжектирования динамического шаблона на страницу.
Возможность создавать ссылки на шаблоны и указывать их другие директивы, такие как ngIf
это только начало.
Мы также можем взять сам шаблон и создать его экземпляр где угодно на странице, используя ngTemplateOutlet
директиву:
<ng-container *ngTemplateOutlet="loading"></ng-container>
Здесь мы используем ng-container
и структурный директиву ngTemplateOutlet
для создания шаблона loading
, который мы определили выше при помощи переменной шаблона #loading
.
Один вопрос насчет шаблона - что мы видим внутри него? Имеет ли шаблон свою собственную область видимости? Какие переменный может видеть шаблон?
Внутри тела ng-template
мы имеем доступ к тому же контексту, который виден во внешнем шаблоне, например, переменной lessons
(то есть ng-template
экземпляр имеет доступ к тому же контексту, в которой он встроен).
Но каждый шаблон также может определить свой собственный набор входящих переменных! Фактически, каждый шаблон имеет связанный объект контекста, содержащий все входные переменные специфичный для шаблоны.
Давайте рассмотрим пример:
@Component({
selector: 'app-root',
template: `
<ng-template #estimateTemplate let-lessonsCounter="estimate">
<div> Approximately {{lessonsCounter}} lessons ...</div>
</ng-template>
<ng-container
*ngTemplateOutlet="estimateTemplate; context: templateCtx"></ng-container>
`})
export class AppComponent {
totalEstimate = 10;
templateCtx = {
estimate: this.totalEstimate
};
}
Особенности:
lessonsCounter
, и определена в ng-template
через префикс let-
lessonsCounter
видна внутри ng-template
, но не снаружиlessonsCounter
равно выражение, которое присвоено let-lessonsCounter
ngTemplateOutlet
вместе с шаблономestimate
, чтобы его значение отображалось внутри шаблонаngTemplateOutlet
через свойство context
Пример выше отрендерит:
Approximately 10 lessons ...
Отличный пример того как определять и создавать наши собственные шаблоны.
Декоратор @ViewChild
позволяет нам получить доступ к дочернему компоненту из родительского компонента.
Возьмем таб контейнер и разрешим пользователям настраивать внешний вид кнопок вкладок (в контексте статьи автор просто передает через декоратор Input
'пользовательский' шаблон с кнопками дочернему компоненту; если его не передать будет использован дефолтный шаблон с кнопками).
Определим шаблон с кнопками в родительском компоненте:
@Component({
selector: 'app-root',
template: `
<ng-template #customTabButtons>
<div class="custom-class">
<button class="tab-button" (click)="login()">
{{loginText}}
</button>
<button class="tab-button" (click)="signUp()">
{{signUpText}}
</button>
</div>
</ng-template>
<tab-container [headerTemplate]="customTabButtons"></tab-container>
`})
export class AppComponent implements OnInit {
}
Затем в компоненте таб контейнера определим входящее свойство, котороя также является шаблоном с именем headerTemplate
.
@Component({
selector: 'tab-container',
template: `
<ng-template #defaultTabButtons>
<div class="default-tab-buttons">
...
</div>
</ng-template>
<ng-container
*ngTemplateOutlet="headerTemplate ? headerTemplate: defaultTabButtons">
</ng-container>
... rest of tab container component ...
`})
export class TabContainerComponent {
@Input()
headerTemplate: TemplateRef<any>;
}
Пара вещей, которые стоит отметить:
defaultTabButtons
headerTemplate
не undefined
headerTemplate
будет использован для показа кнопокng-container
и с использованием ngTemplateOutlet
По сути мы будем использовать пользовательский шаблон, если он есть, или шаблон по умолчанию.
Корневые директивы ng-container
, ng-template
и ngTemplateOutlet
объединяются вместе чтобы позволить нам создавать высоко динамичные и настраиваемые компоненты.
Что такое структурные директивы. Встроенные и собственные структурные директивы.
Централизация логики и упрощение коммуникации между компонентами с помощью сервисов.
В Angular 15 запланировано обновление платформы TypeScript, стабилизация API-интерфейсов автономных компонентов, упрощение создания приложения и предложит новый способ составления логики пользовательского интерфейса.