Spec-Zone.ru › Angular 2

Структурные директивы

Angular обладает мощным движком шаблонов, который позволяет нам легко манипулировать структурой DOM наших элементов.

В этом руководстве рассматривается, как Angular манипулирует DOM с помощью структурных директив и как можно написать свои собственные структурные директивы для выполнения тех же действий.

Содержание

  • Что такое структурные директивы?
  • Пример использования NgIf
  • Префикс звездочка (*)
  • Внутри NgFor
    • микросинтаксис
    • переменные входных данных шаблона
    • одна структурная директива на элемент
  • Внутри директив NgSwitch
  • Предпочитать префикс (*)
  • Элемент <template>
  • Группировка соседних элементов с <ng-container>
  • Написание структурной директивы

Попробуйте пример вживую.

Что такое структурные директивы?

Структурные директивы отвечают за макет HTML. Они формируют или переформировывают структуру DOM, обычно добавляя, удаляя или изменяя элементы.

Как и другие директивы, вы применяете структурную директиву к элементу-хосту. Затем директива выполняет то, что от неё требуется с этим элементом-хостом и его потомками.

Структурные директивы легко распознаются. Звездочка (*) предшествует имени атрибута директивы, как в этом примере.

<div *ngIf="hero" >{{hero.name}}</div>

Нет скобок. Нет круглых скобок. Просто *ngIf присваивается строке.

Вы узнаете в этом руководстве, что звездочка (*) — это удобная запись, а строка — это микросинтаксис, а не обычное выражение шаблона. Angular упрощает эту запись в помеченный <template> который окружает элемент-хост и его потомков. Каждая структурная директива делает что-то другое с этим шаблоном.

Три из распространённых встроенных структурных директив — NgIf, NgFor и NgSwitch... — описаны в руководстве Синтаксис шаблонов и показаны в примерах на протяжении всей документации Angular. Вот пример их использования в шаблоне:

<div *ngIf="hero" >{{hero.name}}</div>

<ul>
  <li *ngFor="let hero of heroes">{{hero.name}}</li>
</ul>

<div [ngSwitch]="hero?.emotion">
  <happy-hero    *ngSwitchCase="'happy'"    [hero]="hero"></happy-hero>
  <sad-hero      *ngSwitchCase="'sad'"      [hero]="hero"></sad-hero>
  <confused-hero *ngSwitchCase="'confused'" [hero]="hero"></confused-hero>
  <unknown-hero  *ngSwitchDefault           [hero]="hero"></unknown-hero>
</div>

В этом руководстве не будет повторяться, как ими пользоваться. Но оно объясняет, как они работают, и как написать свои собственные структурные директивы.

Написание директив

На протяжении этого руководства вы увидите директиву, написанную как в UpperCamelCase, так и в lowerCamelCase. Уже видели NgIf и ngIf. Есть причина. NgIf относится к классу директивы; ngIf относится к имени атрибута директивы.

Класс директивы написан в UpperCamelCase (NgIf). Имя атрибута директивы написано в lowerCamelCase (ngIf). В руководстве упоминается класс директивы, когда обсуждаются её свойства и то, что делает директива. В руководстве упоминается имя атрибута при описании того, как вы применяете директиву к элементу в шаблоне HTML.

Существуют ещё два типа директив Angular, подробно описанных в других местах: (1) компоненты и (2) директив атрибутов.

Компонент управляет областью HTML так же, как и обычный HTML-элемент. Технически это директива с шаблоном.

Директива атрибута изменяет внешний вид или поведение элемента, компонента или другой директивы. Например, встроенная директива NgStyle изменяет несколько стилей элемента одновременно.

Вы можете применить много директив атрибутов к одному элементу-хосту. Вы можете применить только одну структурную директиву к элементу-хосту.

Пример использования NgIf

NgIf является простейшей структурной директивой и наиболее лёгкой для понимания. Она принимает булево выражение и делает весь кусок DOM видимым или скрытым.

<p *ngIf="true">
  Expression is true and ngIf is true.
  This paragraph is in the DOM.
</p>
<p *ngIf="false">
  Expression is false and ngIf is false.
  This paragraph is not in the DOM.
</p>

Директива ngIf не скрывает элементы с помощью CSS. Она физически добавляет и удаляет их из DOM. Убедитесь в этом, используя инструменты разработчика браузера для проверки DOM.

ngIf=false element not in DOM

Верхний абзац находится в DOM. Нижний, не используемый абзац нет; вместо него комментарий о "связываниях шаблонов" (подробнее об этом позже).

Когда условие ложно, NgIf удаляет свой элемент-хост из DOM, отсоединяет его от событий DOM (прикрепления, которые он выполнил), отсоединяет компонент от обнаружения изменений Angular и уничтожает его. Компонент и узлы DOM могут быть удалены из памяти и освободят память.

Почему удаление, а не скрытие?

Директива могла бы скрыть нежелательный абзац, установив его display стиль в none.

<p [style.display]="'block'">
  Expression sets display to "block".
  This paragraph is visible.
</p>
<p [style.display]="'none'">
  Expression sets display to "none".
  This paragraph is hidden but still in the DOM.
</p>

Хотя невидимый, элемент остаётся в DOM.

hidden element still in DOM

Разница между скрытием и удалением не имеет значения для простого абзаца. Она имеет значение, когда элемент-хост прикреплён к ресурсоёмкому компоненту. Поведение такого компонента продолжается, даже когда он скрыт. Компонент остаётся связанным со своим DOM-элементом. Он продолжает прослушивать события. Angular продолжает проверять изменения, которые могут повлиять на привязки данных. Всё, что делал компонент, он продолжает делать.

Несмотря на то, что компонент невидим, — и все его дочерние компоненты — занимают ресурсы. Нагрузка на производительность и память может быть существенной, отзывчивость может ухудшиться, и пользователь ничего не видит.

С положительной стороны, повторное отображение элемента быстро. Состояние компонента сохраняется и готово к отображению. Компонент не перезапускается — операция, которая может быть дорогостоящей. Таким образом, скрытие и отображение иногда правильный подход.

Но в отсутствие веской причины сохранить их, вы должны предпочесть удалять DOM-элементы, которые пользователь не видит, и восстанавливать неиспользуемые ресурсы с помощью структурной директивы, такой как NgIf .

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

Префикс звездочка (*)

Вы, конечно, заметили префикс звездочка (*) к имени директивы и задумались, зачем он нужен и что он делает.

Вот *ngIf отображающий имя героя, если hero существует.

<div *ngIf="hero" >{{hero.name}}</div>

Звездочка — это «синтаксический сахар» для чего-то немного более сложного. Внутренне Angular упрощает его в два этапа. Во-первых, он преобразует *ngIf="..." в атрибут шаблона template="ngIf ...", как показано здесь.

<div template="ngIf hero">{{hero.name}}</div>

Затем он преобразует атрибут шаблона в элемент шаблона, заключённый вокруг элемента-хоста, как показано здесь.

<template [ngIf]="hero">
  <div>{{hero.name}}</div>
</template>
  • Директива *ngIf перемещена в элемент <template> , где она стала привязкой свойства, [ngIf].
  • Остальная часть <div>, включая её атрибут класса, перемещена внутрь элемента <template>.

Ни одна из этих форм фактически не отображается. В DOM отображается только готовый результат.

hero div in DOM

Angular обработало содержимое <template> во время фактического рендеринга и заменило <template> диагностическим комментарием.

Директивы NgFor и NgSwitch... следуют той же схеме.

Внутри *ngFor

Angular преобразует *ngFor аналогичным образом, переходя от синтаксиса со звездочкой (*) через атрибут шаблона к элементу шаблона.

Вот полностью реализованное приложение NgFor, написанное всеми тремя способами:

<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
  ({{i}}) {{hero.name}}
</div>

<div template="ngFor let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
  ({{i}}) {{hero.name}}
</div>

<template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
  <div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</template>

Это явно сложнее, чем ngIf и по праву. Директива NgFor имеет больше функций, как необходимых, так и необязательных, чем NgIf показанная в этом руководстве. Как минимум NgFor требует переменной цикла (let hero) и списка (heroes).

Эти функции включаются в строку, присвоенную ngFor, которую вы пишите в микросинтаксисе Angular.

Всё вне строки ngFor остаётся с элементом-хостом (<div>) при перемещении внутрь <template>. В этом примере [ngClass]="odd" остаётся на <div>.

Микросинтаксис

Микросинтаксис Angular позволяет настроить директиву в компактной, удобной строке. Парсер микросинтаксиса преобразует эту строку в атрибуты на <template>:

  • Ключевое слово let объявляет переменную шаблона ввода, которую вы используете в шаблоне. Переменные ввода в этом примере — hero, i, и odd. Парсер преобразует let hero, let i, и let odd в переменные с именами let-hero, let-i, и let-odd.

  • Парсер микросинтаксиса принимает of и trackby, приводит их к стилю заголовка (of -> Of, trackBy -> TrackBy), и добавляет к ним имя атрибута директивы (ngFor), получая имена ngForOf и ngForTrackBy. Это имена двух NgFor свойств ввода. Таким образом, директива узнаёт, что список — heroes, а функция track-by — trackById.

  • По мере того, как директива NgFor перебирает список, она устанавливает и сбрасывает свойства собственного объекта контекста. К этим свойствам относятся index и odd, а также специальное свойство с именем $implicit.

  • Переменные let-i и let-odd были определены как let i=index и let odd=odd соответственно. Angular устанавливает их в текущее значение свойств контекста index и odd.

  • Свойство контекста для let-hero не было указано. Его предполагаемый источник неявен. Angular устанавливает let-hero в значение свойства контекста $implicit, которое NgFor инициализировало героем для текущей итерации.

  • Руководство по API описывает дополнительные свойства директивы NgFor и свойства контекста.

Эти механизмы микросинтаксиса доступны вам при написании собственных структурных директив. Изучение исходного кода для NgIf и NgFor — отличный способ узнать больше.

Переменная шаблона ввода

Переменная шаблона ввода — это переменная, значение которой вы можете использовать внутри одной инстанции шаблона. В этом примере есть несколько таких переменных: hero, i, и odd. Все они предваряются ключевым словом let.

Переменная шаблона ввода — это не то же самое, что переменная ссылки шаблона, ни семантически, ни синтаксически.

Вы объявляете переменную шаблона ввода, используя ключевое слово let (let hero). Область действия переменной ограничена одной инстанцией повторяемого шаблона. Вы можете использовать то же имя переменной снова в определении других структурных директив.

Вы объявляете переменную шаблона ссылки, предваряя имя переменной # (#var). Переменная ссылки относится к привязанному элементу, компоненту или директиве. К ней можно обратиться в любом месте в целом шаблоне.

Имена переменных шаблона ввода и ссылки имеют свои пространства имён. hero в let hero никогда не является той же переменной, что и hero, объявленная как #hero.

Одна структурная директива на один элемент-хост

Когда-нибудь вам захочется повторить блок HTML, но только если выполняется определённое условие. Вы попробуете поместить как *ngFor, так и *ngIf на один и тот же элемент-хост. Angular не позволит вам этого. Вы можете применить только одну структурную директиву к элементу.

Причина в простоте. Структурные директивы могут выполнять сложные действия с элементом-хостом и его потомками. Когда две директивы претендуют на один и тот же элемент-хост, какая из них имеет приоритет? Какая должна идти первой, NgIf или NgFor? Может ли NgIf отменить действие NgFor? Если да (и кажется, что так и должно быть), как Angular обобщит возможность отмены для других структурных директив?

Нет простых ответов на эти вопросы. Запрещение нескольких структурных директив делает их бесполезными. Для этого случая есть простое решение: поместите *ngIf на контейнерный элемент, который оборачивает элемент *ngFor.

Один или оба элемента могут быть ng-container, чтобы вам не приходилось вводить дополнительные уровни HTML.

Внутри директив NgSwitch

Angular NgSwitch на самом деле представляет собой набор взаимосвязанных директив: NgSwitch, NgSwitchCase, и NgSwitchDefault.

Вот пример.

<div [ngSwitch]="hero?.emotion">
  <happy-hero    *ngSwitchCase="'happy'"    [hero]="hero"></happy-hero>
  <sad-hero      *ngSwitchCase="'sad'"      [hero]="hero"></sad-hero>
  <confused-hero *ngSwitchCase="'confused'" [hero]="hero"></confused-hero>
  <unknown-hero  *ngSwitchDefault           [hero]="hero"></unknown-hero>
</div>

Значение переключателя, присвоенное NgSwitch (hero.emotion), определяет, какие (если есть) случаи переключателя будут отображены.

NgSwitch сам по себе не является структурной директивой. Это атрибутивная директива, которая управляет поведением двух других директив переключателя. Поэтому вы пишете [ngSwitch], а не *ngSwitch.

NgSwitchCase и NgSwitchDefault — структурные директивы. Вы применяете их к элементам с использованием обозначения префикса звёздочки (*).

NgSwitchCase отображает свой элемент-хост, когда его значение совпадает со значением переключателя. NgSwitchDefault отображает свой элемент-хост, когда ни один из соседних элементов NgSwitchCase не совпадает со значением переключателя.

Элемент, к которому вы применяете директиву, является её элементом-хостом. <happy-hero> — элемент-хост для счастливого *ngSwitchCase героя. <unknown-hero> — элемент-хост для *ngSwitchDefault.

Как и другие структурные директивы, NgSwitchCase и NgSwitchDefault можно преобразовать в форму атрибута шаблона.

<div [ngSwitch]="hero?.emotion">
  <happy-hero    template="ngSwitchCase 'happy'"    [hero]="hero"></happy-hero>
  <sad-hero      template="ngSwitchCase 'sad'"      [hero]="hero"></sad-hero>
  <confused-hero template="ngSwitchCase 'confused'" [hero]="hero"></confused-hero>
  <unknown-hero  template="ngSwitchDefault"         [hero]="hero"></unknown-hero>
</div>

Это, в свою очередь, может быть преобразовано в форму элемента <template>.

<div [ngSwitch]="hero?.emotion">
  <template [ngSwitchCase]="'happy'">
    <happy-hero [hero]="hero"></happy-hero>
  </template>
  <template [ngSwitchCase]="'sad'">
    <sad-hero [hero]="hero"></sad-hero>
  </template>
  <template [ngSwitchCase]="'confused'">
    <confused-hero [hero]="hero"></confused-hero>
  </template >
  <template ngSwitchDefault>
    <unknown-hero [hero]="hero"></unknown-hero>
  </template>
</div>

Предпочитайте синтаксис с звёздочкой (*)

Синтаксис с звёздочкой (*) более понятен, чем другие преобразованные формы. Используйте <ng-container>, когда нет отдельного элемента для размещения директивы.

Хотя редко бывает веская причина применять структурную директиву в форме атрибута или элемента шаблона, всё же важно знать, что Angular создаёт <template> и понимать, как это работает. Вы будете обращаться к <template> при написании собственной структурной директивы.

<template>

HTML 5 <template> — формула для отрисовки HTML. Она никогда не отображается напрямую. На самом деле, перед отрисовкой представления Angular заменяет <template> и его содержимое комментарием.

Если нет структурной директивы и вы просто помещаете некоторые элементы в <template>, эти элементы исчезают. Такова судьба среднего "Ура!" в фразе "Ура! Ура! Ура!".

<p>Hip!</p>
<template>
  <p>Hip!</p>
</template>
<p>Hooray!</p>

Angular стирает среднее "Ура!", что делает приветствие немного менее энергичным.

template tag rendering

Структурная директива заставляет <template> работать, как вы увидите, когда напишете свою собственную структурную директиву.

Группировка соседних элементов с помощью <ng-container>

Часто существует корневой элемент, который может и должен разместить структурную директиву. Элемент списка (<li>)—типичный элемент-хост для повторения NgFor.

<li *ngFor="let hero of heroes">{{hero.name}}</li>

Когда нет элемента-хоста, вы обычно можете обернуть содержимое в контейнерный элемент HTML, такой как <div>, и прикрепить директиву к этому обертыванию.

<div *ngIf="hero" >{{hero.name}}</div>

Введение другого контейнерного элемента — обычно <span> или <div> — для группировки элементов под одним корневым элементом обычно безвредно. Обычно... но не всегда.

Элемент группировки может нарушить внешний вид шаблона, так как стили CSS не ожидают и не учитывают новую структуру. Например, предположим, что у вас есть следующая структура абзаца.

<p>
  I turned the corner
  <span *ngIf="hero">
    and saw {{hero.name}}. I waved
  </span>
  and continued on my way.
</p>

У вас также есть правило стиля CSS, которое случайно применяется к <span> внутри абзаца <p>.

p span { color: red; font-size: 70%; }

Сформированный абзац отображается странно.

spanned paragraph with bad style

Стиль p span , предназначенный для использования в другом месте, был случайно применён здесь.

Ещё одна проблема: некоторые элементы HTML требуют, чтобы все непосредственные дочерние элементы были определённого типа. Например, элемент <select> требует <option> дочерние элементы. Вы не можете обернуть варианты в условный <div> или <span>.

Когда вы попробуете это,

<div>
  Pick your favorite hero
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <span *ngFor="let h of heroes">
    <span *ngIf="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </span>
  </span>
</select>

выпадающий список пуст.

spanned options don't work

Браузер не отобразит <option> внутри <span>.

<ng-container> на помощь

Angular <ng-container> — это элемент группировки, который не мешает стилям или макетам, потому что Angular не помещает его в DOM.

Вот условный абзац ещё раз, на этот раз с использованием <ng-container>.

<p>
  I turned the corner
  <ng-container *ngIf="hero">
    and saw {{hero.name}}. I waved
  </ng-container>
  and continued on my way.
</p>

Он отображается правильно.

ngcontainer paragraph with proper style

Теперь условно исключите выпадающий список <option> с помощью <ng-container>.

<div>
  Pick your favorite hero
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <ng-container *ngFor="let h of heroes">
    <ng-container *ngIf="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </ng-container>
  </ng-container>
</select>

Выпадающий список работает правильно.

ngcontainer options work properly

<ng-container> — это синтаксический элемент, распознаваемый парсером Angular. Это не директива, компонент, класс или интерфейс. Это больше похоже на фигурные скобки в блоке JavaScript if:

if (someCondition) {
  statement1;
  statement2;
  statement3;
} 

Без этих скобок JavaScript бы выполнял только первое утверждение, когда вы хотите условно выполнить все из них как единый блок. <ng-container> удовлетворяет аналогичной потребности в шаблонах Angular.

Напишите структурную директиву

В этом разделе вы пишете структурную директиву UnlessDirective, которая делает обратное NgIf. NgIf отображает содержимое шаблона, когда условие true. UnlessDirective отображает содержимое, когда условие ложно.

<p *myUnless="condition">Show this sentence unless the condition is true.</p>

Создание директивы аналогично созданию компонента.

  • Импортируйте декоратор Directive (вместо декоратора Component).

  • Импортируйте символы Input, TemplateRef, и ViewContainerRef; вам они понадобятся для любой структурной директивы.

  • Примените декоратор к классу директивы.

  • Установите CSS селектор атрибута, который определяет директиву при её применении к элементу в шаблоне.

Вот как вы можете начать:

src/app/unless.directive.ts (скелет)

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

@Directive({ selector: '[myUnless]'})
export class UnlessDirective {
}

Селектор директивы обычно является именем атрибута директивы в квадратных скобках, [myUnless]. Скобки определяют CSS селектор атрибута.

Имя атрибута директивы должно быть написано в lowerCamelCase и начинаться с префикса. Не используйте ng. Этот префикс принадлежит Angular. Выберите короткий префикс, подходящий вам или вашей компании. В этом примере префикс my.

Имя класса директивы заканчивается на Directive в соответствии с руководством по стилю. Собственные директивы Angular так не делают.

TemplateRef и ViewContainerRef

Простая структурная директива, подобная этой, создаёт встроенный вид из сгенерированного Angular <template> и вставляет этот вид в контейнер представления рядом с исходным элементом-хостом директивы <p>.

Вы получите содержимое <template> с помощью TemplateRef и получите доступ к контейнеру представления через ViewContainerRef.

Оба объекта вы инжектируете в конструктор директивы в качестве закрытых переменных класса.

constructor(
  private templateRef: TemplateRef<any>,
  private viewContainer: ViewContainerRef) { }

Свойство myUnless

Потребитель директивы ожидает привязать истинное/ложное условие к [myUnless]. Это означает, что директива нуждается в свойстве myUnless, украшенном @Input.

Узнайте о @Input в руководстве Синтаксис шаблонов.

@Input() set myUnless(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 устанавливает свойство myUnless всякий раз, когда значение условия меняется. Поскольку свойство myUnless работает, оно нуждается в методе-сеттере.

  • Если условие ложно, а представление ещё не создано, сообщите контейнеру представления создать встроенный вид из шаблона.

  • Если условие истинно и представление отображается, очистите контейнер, что также уничтожит представление.

Никто не читает свойство myUnless, поэтому оно не нуждается в методе-геттере.

Полный код директивы выглядит так:

src/app/unless.directive.ts (фрагмент)

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

/**
 * Add the template content to the DOM unless the condition is true.
 */
@Directive({ selector: '[myUnless]'})
export class UnlessDirective {
  private hasView = false;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef) { }

  @Input() set myUnless(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;
    }
  }
}

Добавьте эту директиву в массив declarations модуля AppModule.

Затем создайте HTML, чтобы попробовать её.

<p *myUnless="condition" class="unless a">
  (A) This paragraph is displayed because the condition is false.
</p>

<p *myUnless="!condition" class="unless b">
  (B) Although the condition is true,
  this paragraph is displayed because myUnless is set to false.
</p>

Когда condition ложно, верхний (A) абзац отображается, а нижний (B) абзац скрывается. Когда condition истинно, верхний (A) абзац скрывается, а нижний (B) абзац отображается.

UnlessDirective in action

Резюме

Вы можете как попробовать, так и скачать исходный код этого руководства в примере на живом сайте.

Вот исходный код из папки src/app/.

app.component.ts
import { Component } from '@angular/core';

import { Hero, heroes } from './hero';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent {
  heroes = heroes;
  hero = this.heroes[0];

  condition = false;
  logs: string[] = [];
  showSad = true;
  status = 'ready';

  trackById(index: number, hero: Hero): number { return hero.id; }
}
app.component.html
<h1>Structural Directives</h1>

<p>Conditional display of hero</p>

<blockquote>
<div *ngIf="hero" >{{hero.name}}</div>
</blockquote>

<p>List of heroes</p>

<ul>
  <li *ngFor="let hero of heroes">{{hero.name}}</li>
</ul>


<hr>

<h2 id="ngIf">NgIf</h2>

<p *ngIf="true">
  Expression is true and ngIf is true.
  This paragraph is in the DOM.
</p>
<p *ngIf="false">
  Expression is false and ngIf is false.
  This paragraph is not in the DOM.
</p>

<p [style.display]="'block'">
  Expression sets display to "block".
  This paragraph is visible.
</p>
<p [style.display]="'none'">
  Expression sets display to "none".
  This paragraph is hidden but still in the DOM.
</p>

<h4>NgIf with template</h4>
<p>&lt;template&gt; element</p>
<template [ngIf]="hero">
  <div>{{hero.name}}</div>
</template>

<p>template attribute</p>
<div template="ngIf hero">{{hero.name}}</div>

<hr>

<h2 id="ng-container">&lt;ng-container&gt;</h2>

<h4>*ngIf with a &lt;ng-container&gt;</h4>

<button (click)="hero = hero ? null : heroes[0]">Toggle hero</button>

<p>
  I turned the corner
  <ng-container *ngIf="hero">
    and saw {{hero.name}}. I waved
  </ng-container>
  and continued on my way.
</p>
<p>
  I turned the corner
  <span *ngIf="hero">
    and saw {{hero.name}}. I waved
  </span>
  and continued on my way.
</p>

<p><i>&lt;select&gt; with &lt;span&gt;</i></p>
<div>
  Pick your favorite hero
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <span *ngFor="let h of heroes">
    <span *ngIf="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </span>
  </span>
</select>

<p><i>&lt;select&gt; with &lt;ng-container&gt;</i></p>
<div>
  Pick your favorite hero
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <ng-container *ngFor="let h of heroes">
    <ng-container *ngIf="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </ng-container>
  </ng-container>
</select>
<br><br>

<hr>

<h2 id="ngFor">NgFor</h2>

<div class="box">

<p class="code">&lt;div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"&gt;</p>
<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
  ({{i}}) {{hero.name}}
</div>

<p class="code">&lt;div template="ngFor let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"&gt;</p>
<div template="ngFor let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
  ({{i}}) {{hero.name}}
</div>

<p class="code">&lt;template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"&gt;</p>
<template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
  <div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</template>

</div>
<hr>

<h2 id="ngSwitch">NgSwitch</h2>

<div>Pick your favorite hero</div>
<p>
  <label *ngFor="let h of heroes">
    <input type="radio" name="heroes" [(ngModel)]="hero" [value]="h">{{h.name}}
  </label>
  <label><input type="radio" name="heroes" (click)="hero = null">None of the above</label>
</p>

<h4>NgSwitch</h4>

<div [ngSwitch]="hero?.emotion">
  <happy-hero    *ngSwitchCase="'happy'"    [hero]="hero"></happy-hero>
  <sad-hero      *ngSwitchCase="'sad'"      [hero]="hero"></sad-hero>
  <confused-hero *ngSwitchCase="'confused'" [hero]="hero"></confused-hero>
  <unknown-hero  *ngSwitchDefault           [hero]="hero"></unknown-hero>
</div>

<h4>NgSwitch with <i>template</i> attribute</h4>
<div [ngSwitch]="hero?.emotion">
  <happy-hero    template="ngSwitchCase 'happy'"    [hero]="hero"></happy-hero>
  <sad-hero      template="ngSwitchCase 'sad'"      [hero]="hero"></sad-hero>
  <confused-hero template="ngSwitchCase 'confused'" [hero]="hero"></confused-hero>
  <unknown-hero  template="ngSwitchDefault"         [hero]="hero"></unknown-hero>
</div>

<h4>NgSwitch with &lt;template&gt;</h4>
<div [ngSwitch]="hero?.emotion">
  <template [ngSwitchCase]="'happy'">
    <happy-hero [hero]="hero"></happy-hero>
  </template>
  <template [ngSwitchCase]="'sad'">
    <sad-hero [hero]="hero"></sad-hero>
  </template>
  <template [ngSwitchCase]="'confused'">
    <confused-hero [hero]="hero"></confused-hero>
  </template >
  <template ngSwitchDefault>
    <unknown-hero [hero]="hero"></unknown-hero>
  </template>
</div>

<hr>

<h2>&lt;template&gt;</h2>
<p>Hip!</p>
<template>
  <p>Hip!</p>
</template>
<p>Hooray!</p>

<hr>

<h2 id="myUnless">UnlessDirective</h2>
<p>
  The condition is currently
  <span [ngClass]="{ a: !condition, b: condition, unless: true }">{{condition}}</span>.
  <button
    (click)="condition = !condition"
    [ngClass] = "{ a: condition, b: !condition }" >
    Toggle condition to {{condition ? 'false' : 'true'}}
  </button>
</p>
<p *myUnless="condition" class="unless a">
  (A) This paragraph is displayed because the condition is false.
</p>

<p *myUnless="!condition" class="unless b">
  (B) Although the condition is true,
  this paragraph is displayed because myUnless is set to false.
</p>


<h4>UnlessDirective with template</h4>

<p *myUnless="condition">Show this sentence unless the condition is true.</p>

<p template="myUnless condition" class="code unless">
  (A) &lt;p template="myUnless condition" class="code unless"&gt;
</p>

<template [myUnless]="condition">
  <p class="code unless">
    (A) &lt;template [myUnless]="condition"&gt;
  </p>
</template>
app.component.css
button {
  min-width: 100px;
  font-size: 100%;
}

.box {
  border: 1px solid gray;
  max-width: 600px;
  padding: 4px;
}
.choices {
  font-style: italic;
}

code, .code {
  background-color: #eee;
  color: black;
  font-family: Courier, sans-serif;
  font-size: 85%;
}

div.code {
  width: 400px;
}

.heroic {
  font-size: 150%;
  font-weight: bold;
}

hr {
  margin: 40px 0
}

.odd {
  background-color:  palegoldenrod;
}

td, th {
  text-align: left;
  vertical-align: top;
}

p span { color: red; font-size: 70%; }

.unless {
  border: 2px solid;
  padding: 6px;
}

p.unless {
  width: 500px;
}

button.a, span.a, .unless.a {
  color: red;
  border-color: gold;
  background-color: yellow;
  font-size: 100%;
}

button.b, span.b, .unless.b {
  color: black;
  border-color: green;
  background-color: lightgreen;
  font-size: 100%;
}
app.module.ts
import { NgModule }      from '@angular/core';
import { FormsModule }   from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }         from './app.component';
import { heroSwitchComponents } from './hero-switch.components';
import { UnlessDirective }    from './unless.directive';

@NgModule({
  imports: [ BrowserModule, FormsModule ],
  declarations: [
    AppComponent,
    heroSwitchComponents,
    UnlessDirective
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }
hero.ts
export class Hero {
  id: number;
  name: string;
  emotion?: string;
}

export const heroes: Hero[] = [
  { id: 1, name: 'Mr. Nice',  emotion: 'happy'},
  { id: 2, name: 'Narco',     emotion: 'sad' },
  { id: 3, name: 'Windstorm', emotion: 'confused' },
  { id: 4, name: 'Magneta'}
];
hero-switch.components.ts
import { Component, Input } from '@angular/core';
import { Hero } from './hero';

@Component({
  selector: 'happy-hero',
  template: `Wow. You like {{hero.name}}. What a happy hero ... just like you.`
})
export class HappyHeroComponent {
  @Input() hero: Hero;
}

@Component({
  selector: 'sad-hero',
  template: `You like {{hero.name}}? Such a sad hero. Are you sad too?`
})
export class SadHeroComponent {
  @Input() hero: Hero;
}

@Component({
  selector: 'confused-hero',
  template: `Are you as confused as {{hero.name}}?`
})
export class ConfusedHeroComponent {
  @Input() hero: Hero;
}

@Component({
  selector: 'unknown-hero',
  template: `{{message}}`
})
export class UnknownHeroComponent {
  @Input() hero: Hero;
  get message() {
    return this.hero && this.hero.name ?
      `${this.hero.name} is strange and mysterious.` :
      'Are you feeling indecisive?';
  }
}

export const heroSwitchComponents =
  [ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ];
unless.directive.ts
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

/**
 * Add the template content to the DOM unless the condition is true.
 *
 * If the expression assigned to `myUnless` evaluates to a truthy value
 * then the templated elements are removed removed from the DOM,
 * the templated elements are (re)inserted into the DOM.
 *
 * <div *ngUnless="errorCount" class="success">
 *   Congrats! Everything is great!
 * </div>
 *
 * ### Syntax
 *
 * - `<div *myUnless="condition">...</div>`
 * - `<div template="myUnless condition">...</div>`
 * - `<template [myUnless]="condition"><div>...</div></template>`
 *
 */
@Directive({ selector: '[myUnless]'})
export class UnlessDirective {
  private hasView = false;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef) { }

  @Input() set myUnless(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;
    }
  }
}

Вы узнали

  • что структурные директивы управляют макетом HTML.
  • как использовать <ng-container> в качестве группирующего элемента, когда нет подходящего элемента-хоста.
  • что Angular обрабатывает синтаксис с звёздочкой (*) в <template>.
  • как это работает для встроенных директив NgIf, NgFor и NgSwitch.
  • о микросинтаксисе, который расширяется в <template>.
  • как написать пользовательскую структурную директиву, UnlessDirective.

© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://v2.angular.io/docs/ts/latest/guide/structural-directives.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API