Структурные директивы¶
28.02.2022
Это руководство посвящено структурным директивам и содержит концептуальную информацию о том, как работают такие директивы, как Angular интерпретирует их сокращенный синтаксис, и как добавить свойства защиты шаблона для отлова ошибок типа шаблона.
Структурные директивы — это директивы, которые изменяют макет DOM путем добавления и удаления элементов DOM.
Angular предоставляет набор встроенных структурных директив (таких как NgIf
, NgForOf
, NgSwitch
и другие), которые часто используются во всех проектах Angular. Более подробную информацию можно найти в Встроенные директивы.
Пример приложения, которое описывается на этой странице.
Сокращение структурных директив¶
Когда применяются структурные директивы, они обычно сопровождаются звездочкой, *
, например, *ngIf
. Это соглашение является сокращением, которое Angular интерпретирует и преобразует в более длинную форму. Angular преобразует звездочку перед структурной директивой в <ng-template>
, который окружает основной элемент и его потомков.
Например, возьмем следующий код, который использует *ngIf
для отображения имени героя, если hero
существует:
1 |
|
В Angular создается элемент <ng-template>
и к нему применяется директива *ngIf
, где она становится свойством, заключенным в квадратные скобки, [ngIf]
. Затем остальная часть <div>
, включая его атрибут class, перемещается внутрь <ng-template>
:
1 2 3 |
|
Обратите внимание, что Angular не создает реальный элемент <ng-template>
, а вместо этого только отображает элемент <div>
.
1 |
|
В следующем примере сравнивается сокращенное использование звездочки в *ngFor
с длинной формой <ng-template>
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Здесь все, что связано со структурной директивой ngFor
, переносится в <ng-template>
. Все остальные привязки и атрибуты элемента применяются к элементу <div>
внутри <ng-template>
.
Другие модификаторы принимающего элемента, помимо строки ngFor
, остаются на месте по мере перемещения элемента внутрь <ng-template>
.
В этом примере [class.odd]="odd"
остается на <div>
.
Ключевое слово let
объявляет входную переменную шаблона, на которую вы можете ссылаться внутри шаблона. В данном примере входными переменными являются hero
, i
и odd
.
Парсер переводит let hero
, let i
и let odd
в переменные с именами let-hero
, let-i
и let-odd
.
Переменные let-i
и let-odd
становятся let i=index
и let odd=odd
.
Angular устанавливает i
и odd
в текущее значение свойств контекста index
и odd
.
Парсер применяет PascalCase ко всем директивам и префиксирует их именем атрибута директивы, например, ngFor. Например, входные свойства ngFor
, of
и trackBy
, отображаются на ngForOf
и ngForTrackBy
.
По мере того, как директива NgFor
проходит по списку, она устанавливает и сбрасывает свойства своего контекстного объекта. Эти свойства могут включать, но не ограничиваться, index
, odd
и специальное свойство под названием $implicit
.
Angular устанавливает let-hero
в значение свойства контекста $implicit
, которое NgFor
инициализировал героем для текущей итерации.
Для получения дополнительной информации смотрите документацию NgFor API и NgForOf API.
Обратите внимание, что элемент <ng-template>
в Angular определяет шаблон, который по умолчанию ничего не отображает, если вы просто обернете элементы в <ng-template>
без применения структурной директивы, эти элементы не будут отображены.
Для получения дополнительной информации смотрите документацию ng-template API.
Одна структурная директива на элемент¶
Довольно распространенным случаем является повторение блока HTML, но только при выполнении определенного условия. Интуитивно понятным способом сделать это является размещение *ngFor
и *ngIf
на одном и том же элементе. Однако, поскольку и *ngFor
, и *ngIf
являются структурными директивами, это будет расценено компилятором как ошибка. Вы можете применить только одну структурную директиву к элементу.
Причина в простоте. Структурные директивы могут делать сложные вещи с основным элементом и его потомками.
Когда две директивы претендуют на один и тот же элемент-хозяин, какая из них должна иметь приоритет?
Что должно быть первым, NgIf
или NgFor
? Может ли NgIf
отменить эффект NgFor
? Если да (а кажется, что так и должно быть), то как Angular должен обобщить возможность отмены для других структурных директив?
На эти вопросы нет простых ответов. Запрет на использование нескольких структурных директив делает их спорными. Есть простое решение для этого случая использования: поместите *ngIf
в элемент-контейнер, который обертывает элемент *ngFor
. Один или оба элемента могут быть <ng-container>
, чтобы не создавать лишних элементов DOM.
Создание структурной директивы¶
В этом разделе вы узнаете, как создать UnlessDirective
и как задать значения condition
. Директива UnlessDirective
делает противоположное NgIf
, а значения condition
могут быть установлены в true
или false
.
NgIf
отображает содержимое шаблона, когда условие равно true
.
UnlessDirective
отображает содержимое, когда условие равно false
.
Ниже приведен селектор UnlessDirective
, appUnless
, примененный к элементу paragraph. Когда condition
равно false
, браузер отображает предложение.
1 2 3 |
|
-
Используя Angular CLI, выполните следующую команду, где
unless
— имя директивы:1
ng generate directive unless
Angular создает класс директивы и определяет CSS-селектор
appUnless
, который идентифицирует директиву в шаблоне. -
Импортируйте
Input
,TemplateRef
иViewContainerRef
.1 2 3 4 5 6 7 8 9
import { Directive, Input, TemplateRef, ViewContainerRef, } from '@angular/core'; @Directive({ selector: '[appUnless]' }) export class UnlessDirective {}
-
Вставьте
TemplateRef
иViewContainerRef
в конструктор директивы как приватные переменные.1 2 3 4
constructor( private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef ) { }
Директива
UnlessDirective
создает встроенное представление из сгенерированного Angular<ng-template>
и вставляет это представление в контейнер представления рядом с исходным элементом директивы<p>
.TemplateRef
помогает добраться до содержимого<ng-template>
, аViewContainerRef
открывает доступ к контейнеру представления. -
Добавьте свойство
appUnless
@Input()
с сеттером.1 2 3 4 5 6 7 8 9
@Input() set appUnless(condition: boolean) { if (!condition && !this.hasView) { this.viewContainer.createEmbeddedView(this.templateRef); this.hasView = true; } else if (condition && this.hasView) { this.viewContainer.clear(); this.hasView = false; } }
Angular устанавливает свойство
appUnless
всякий раз, когда значение условия изменяется.- Если условие ложно и Angular не создал представление ранее, то сеттер заставляет контейнер представления создать встроенное представление из шаблона.
- Если условие истинно, и представление отображается в данный момент, сеттер очищает контейнер, который утилизирует представление.
Полный текст директивы выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
Тестирование директивы¶
В этом разделе вы обновите свое приложение, чтобы протестировать UnlessDirective
.
-
Добавьте
condition
, установленное вfalse
вAppComponent
.1
condition = false;
-
Обновите шаблон для использования директивы.
Здесь
*appUnless
находится на двух тегах<p>
с противоположными значениямиcondition
, одноtrue
и одноfalse
.1 2 3 4 5 6 7 8 9
<p *appUnless="condition" class="unless a"> (A) This paragraph is displayed because the condition is false. </p> <p *appUnless="!condition" class="unless b"> (B) Although the condition is true, this paragraph is displayed because appUnless is set to false. </p>
Звездочка — это сокращение, обозначающее
appUnless
как структурную директиву.Когда
condition
ложно, верхний (A) параграф появляется, а нижний (B) параграф исчезает.Когда
condition
истинно, верхний (A) параграф исчезает, а нижний (B) параграф появляется. -
Чтобы изменить и отобразить значение
условия
в браузере, добавьте разметку, отображающую статус и кнопку.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<p> The condition is currently <span [ngClass]="{ 'a': !condition, 'b': condition, 'unless': true }" >{{condition}}</span >. <button type="button" (click)="condition = !condition" [ngClass]="{ 'a': condition, 'b': !condition }" > Toggle condition to {{condition ? 'false' : 'true'}} </button> </p>
Чтобы убедиться, что директива работает, нажмите на кнопку, чтобы изменить значение condition
.
Справочник по синтаксису структурных директив¶
Когда вы пишете свои собственные структурные директивы, используйте следующий синтаксис:
1 |
|
В следующих таблицах описана каждая часть грамматики структурной директивы:
1 |
|
1 |
|
1 |
|
Keyword | Details |
---|---|
prefix | Ключ атрибута HTML |
key | Ключ атрибута HTML |
local | Имя локальной переменной, используемой в шаблоне |
export | Значение, экспортируемое директивой под заданным именем |
expression | Стандартное угловое выражение |
Как Angular переводит стенографические директивы¶
Angular переводит сокращение структурных директив в обычный синтаксис связывания следующим образом:
Сокращение | Translation |
---|---|
prefix and naked expression | [prefix]="expression" |
keyExp | [prefixKey] "expression" (let-prefixKey="export") prefix добавляется к key |
let | let-local="export" |
Примеры сокращений¶
В следующей таблице приведены примеры сокращений:
Сокращение | Как Angular интерпретирует синтаксис | ||||
---|---|---|---|---|---|
|
| ||||
|
| ||||
|
| ||||
|
|
Улучшение проверки типов шаблонов для пользовательских директив¶
Вы можете улучшить проверку типов шаблонов для пользовательских директив, добавив свойства защиты шаблона в определение директивы. Эти свойства помогают программе проверки типов шаблонов Angular находить ошибки в шаблоне во время компиляции, что позволяет избежать ошибок во время выполнения.
Эти свойства выглядят следующим образом:
- Свойство
ngTemplateGuard_(someInputProperty)
позволяет вам указать более точный тип для входного выражения в шаблоне. - Статическое свойство
ngTemplateContextGuard
объявляет тип контекста шаблона.
В этом разделе приведены примеры обоих типов свойств защиты типа. Для получения дополнительной информации смотрите Проверка типов шаблонов.
Уточнение требований к типам в шаблоне с помощью защит шаблона¶
Структурная директива в шаблоне управляет тем, будет ли этот шаблон отображаться во время выполнения, основываясь на его входном выражении. Чтобы помочь компилятору отловить ошибки типа шаблона, вы должны как можно точнее определить требуемый тип входного выражения директивы, когда оно встречается внутри шаблона.
Функция защиты типа сужает ожидаемый тип входного выражения до подмножества типов, которые могут быть переданы директиве внутри шаблона во время выполнения. Вы можете предоставить такую функцию, чтобы помочь программе проверки типов вывести правильный тип для выражения во время компиляции.
Например, реализация NgIf
использует сужение типов, чтобы гарантировать, что шаблон будет инстанцирован только в том случае, если входное выражение *ngIf
истинно. Для обеспечения конкретного требования к типу, директива NgIf
определяет статическое свойство ngTemplateGuard_ngIf: 'binding'
.
Значение binding
является особым случаем для распространенного вида сужения типа, когда входное выражение оценивается для того, чтобы удовлетворить требованию типа.
Чтобы задать более конкретный тип входного выражения для директивы в шаблоне, добавьте к директиве свойство ngTemplateGuard_xx
, где суффикс к имени статического свойства, xx
, является именем поля @Input()
. Значением свойства может быть либо общая функция сужения типов на основе ее возвращаемого типа, либо строка "binding"
, как в случае NgIf
.
Например, рассмотрим следующую структурную директиву, которая принимает результат шаблонного выражения в качестве входных данных:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
1 2 3 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
В данном примере тип LoadingState<T>
допускает одно из двух состояний, Loaded<T>
или Loading
. Выражение, используемое в качестве входного state
директивы (псевдоним appIfLoaded
), имеет зонтичный тип LoadingState
, поскольку неизвестно, в каком состоянии находится загрузка в данный момент.
Определение IfLoadedDirective
объявляет статическое поле ngTemplateGuard_appIfLoaded
, которое выражает поведение сужения. Внутри шаблона AppComponent
структурная директива *appIfLoaded
должна выводить этот шаблон только тогда, когда state
действительно Loaded<Hero>
.
Защита типа позволяет программе проверки типов сделать вывод, что допустимым типом state
в шаблоне является Loaded<T>
, и далее сделать вывод, что T
должен быть экземпляром Hero
.
Типизация контекста директивы¶
Если ваша структурная директива предоставляет контекст инстанцированному шаблону, вы можете правильно типизировать его внутри шаблона, предоставив статическую функцию ngTemplateContextGuard
. В следующем фрагменте показан пример такой функции.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
1 2 3 4 5 |
|