Spec-Zone.ru › Angular 2

Синтаксис шаблонов

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

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

Вам, возможно, знакома двойственность компонент/шаблон из опыта работы с модель-представление-контроллер (MVC) или модель-представление-модель-представления (MVVM). В Angular компонент играет роль контроллера/модели представления, а шаблон представляет собой представление.

Содержание

Это руководство охватывает основные элементы синтаксиса шаблонов Angular, необходимые для построения представления:

  • HTML в шаблонах
  • Интерполяция ( {{...}} )
  • Выражения шаблонов
  • Операторы шаблонов
  • Синтаксис привязки
  • Привязка свойств ( [property] )
  • Привязки атрибутов, классов и стилей
  • Привязка событий ( (event) )
  • Двухсторонняя привязка данных ( [(...)] )
  • Встроенные директивы
  • Встроенные атрибутивные директивы
    • NgClass
    • NgStyle
    • NgModel ([(ngModel)])
  • Встроенные структурные директивы
    • NgIf
    • NgFor
      • Переменные ввода шаблона
      • Микросинтаксис
    • Директива NgSwitch
  • Переменные ссылок шаблона ( #var )
  • Свойства ввода и вывода ( @Input и @Output )
  • Операторы выражений шаблонов
    • pipe ( | )
    • Оператор безопасной навигации ( ?. )

Живой пример демонстрирует весь синтаксис и фрагменты кода, описанные в этом руководстве.

HTML в шаблонах

HTML — это язык шаблонов Angular. Практически весь синтаксис HTML является допустимым синтаксисом шаблонов. Элемент <script> является заметным исключением; он запрещен, устраняя риск атак с внедрением сценариев. На практике <script> игнорируется, и в консоли браузера появляется предупреждение. Подробности см. на странице Безопасность.

Некоторые допустимые HTML-элементы не имеют большого смысла в шаблоне. Элементы <html>, <body>, и <base> не выполняют полезной функции. Практически все остальное разрешено.

Вы можете расширить словарный запас HTML своих шаблонов с помощью компонентов и директив, которые появляются как новые элементы и атрибуты. В следующих разделах вы узнаете, как динамически получать и устанавливать значения DOM (Document Object Model) с помощью привязки данных.

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

Вернуться к содержанию

Интерполяция ( {{...}} )

Вы познакомились с двойными фигурными скобками интерполяции, {{ и }}, на ранних этапах изучения Angular.

<p>My current hero is {{currentHero.name}}</p>

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

<h3>
  {{title}}
  <img src="{{heroImageUrl}}" style="height:30px">
</h3>

Текст в фигурных скобках часто является именем свойства компонента. Angular заменяет это имя строковым значением соответствующего свойства компонента. В приведенном выше примере Angular вычисляет свойства title и heroImageUrl и «заполняет пробелы», сначала отображая заголовок приложения жирным шрифтом, а затем героическое изображение.

В более общем случае текст в фигурных скобках — это выражение шаблона, которое Angular сначала вычисляет, а затем преобразует в строку. Следующая интерполяция демонстрирует этот момент, добавляя два числа:

<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}</p>

Выражение может вызывать методы хост-компонента, такие как getVal(), как показано здесь:

<!-- "The sum of 1 + 1 is not 4" -->
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>

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

По-видимому, вы вставляете результат между тегами элементов и присваиваете его атрибутам. Это удобно, и вы редко страдаете от этой ошибки. Хотя это не совсем так. Интерполяция — это специальный синтаксис, который Angular преобразует в привязку свойств, как объяснено ниже.

Но сначала давайте более подробно рассмотрим выражения и операторы шаблонов.

Вернуться к содержанию

Выражения шаблонов

Выражение шаблона вычисляет значение. Angular выполняет выражение и присваивает его свойству целевого элемента привязки; целью может быть HTML-элемент, компонент или директива.

Фигурные скобки интерполяции в {{1 + 1}} окружают выражение шаблона 1 + 1. В разделе привязки свойств ниже выражение шаблона находится в кавычках справа от символа = как в [property]="expression".

Эти выражения шаблонов написаны на языке, похожем на JavaScript. Многие выражения JavaScript являются допустимыми выражениями шаблонов, но не все.

Выражения JavaScript, имеющие или способствующие побочным эффектам, запрещены, включая:

  • присваивания (=, +=, -=, ...)
  • new
  • цепление выражений с помощью ; или ,
  • операторы инкремента и декремента (++ и --)

Другие заметные отличия от синтаксиса JavaScript включают:

  • отсутствие поддержки побитовых операторов | и &
  • новые операторы выражений шаблонов, такие как | и ?.

Контекст выражения

Контекстом выражения обычно является экземпляр компонента. В следующих фрагментах кода title в двойных фигурных скобках и isUnchanged в кавычках относятся к свойствам AppComponent.

{{title}}
<span [hidden]="isUnchanged">changed</span>

Выражение также может ссылаться на свойства контекста шаблона, такие как переменная ввода шаблона (let hero) или переменная ссылки шаблона (#heroInput).

<div *ngFor="let hero of heroes">{{hero.name}}</div>
<input #heroInput> {{heroInput.value}}

Контекст для терминов в выражении — это сочетание переменных шаблона, объекта контекста директивы (если он есть) и членов компонента. Если вы ссылаетесь на имя, принадлежащее нескольким из этих пространств имен, имя переменной шаблона имеет приоритет, за ним следует имя в контексте директивы, а в последнюю очередь — имена членов компонента.

Предыдущий пример представляет собой такое столкновение имен. Компонент имеет свойство hero, а *ngFor определяет переменную шаблона hero.

hero в {{hero.name}} ссылается на переменную ввода шаблона, а не на свойство компонента.

Выражения шаблонов не могут ссылаться на что-либо в глобальном пространстве имен. Они не могут ссылаться на window или document. Они не могут вызывать console.log или Math.max. Они ограничены ссылками на члены контекста выражения.

Вернуться к содержанию

Правила выражений

Выражения шаблонов могут быть критичными для приложения. Следуйте этим правилам:

  • Без видимых побочных эффектов
  • Быстрое выполнение
  • Простота
  • Идемпотентность

Исключения из этих правил должны быть в определенных обстоятельствах, которые вы полностью понимаете.

Без видимых побочных эффектов

Выражение шаблона не должно изменять какой-либо состояние приложения, кроме значения целевого свойства.

Это правило имеет решающее значение для политики «однонаправленного потока данных» Angular. Вам не нужно беспокоиться о том, что чтение значения компонента может изменить какое-то другое отображаемое значение. Представление должно быть стабильным во время одного прохода рендеринга.

Быстрое выполнение

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

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

Простота

Хотя можно написать довольно сложные выражения шаблонов, следует этого избегать.

Имя свойства или вызов метода должны быть нормой. Временное отрицание булевого значения (!) допустимо. В противном случае, ограничьте логику приложения и бизнеса самим компонентом, где его будет проще разрабатывать и тестировать.

Идемпотентность

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

В терминах Angular идемпотентное выражение всегда возвращает ровно то же самое, пока не изменится одно из его зависимых значений.

Зависимые значения не должны изменяться во время одного цикла обработки событий. Если идемпотентное выражение возвращает строку или число, оно возвращает ту же строку или то же число при вызове дважды подряд. Если выражение возвращает объект (включая Array), оно возвращает ту же ссылку на объект при вызове дважды подряд.

Вернуться к содержанию

Операторы шаблонов

Оператор шаблона реагирует на событие, поднятое целевым элементом, компонентом или директивой. Вы увидите операторы шаблонов в разделе привязки событий, где они находятся в кавычках справа от символа = как в (event)="statement".

<button (click)="deleteHero()">Delete hero</button>

Выражение шаблона имеет побочный эффект. В этом и заключается смысл события. Оно позволяет обновлять состояние приложения в ответ на действия пользователя.

Обработка событий — это другая сторона «однонаправленного потока данных» Angular. Вы можете изменять что угодно, где угодно, в течение этого цикла обработки событий.

Как и выражения шаблонов, шаблонные высказывания используют язык, похожий на JavaScript. Парсер шаблонных высказываний отличается от парсера выражений шаблонов и специально поддерживает как базовые присваивания (=), так и цепочки выражений (с помощью ; или ,).

Однако некоторые синтаксические конструкции JavaScript запрещены:

  • new
  • операторы инкремента и декремента, ++ и --
  • присваивания с операторами, такие как += и -=
  • битовые операторы | и &
  • операторы выражений шаблонов операторы выражений шаблонов

Контекст высказываний

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

Контекст высказываний обычно является экземпляром компонента. deleteHero в (click)="deleteHero()" — это метод связанного с данными компонента.

<button (click)="deleteHero()">Delete hero</button>

Контекст высказываний также может ссылаться на свойства собственного контекста шаблона. В следующих примерах в метод обработки события компонента передаются объект шаблона $event, переменная ввода шаблона переменная ввода шаблона (let hero) и переменная ссылки шаблона переменная ссылки шаблона (#heroForm).

<button (click)="onSave($event)">Save</button>
<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>
<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>

Имена контекстов шаблонов имеют приоритет над именами контекстов компонентов. В deleteHero(hero) выше hero — это переменная ввода шаблона, а не свойство hero компонента.

Шаблонные высказывания не могут ссылаться на что-либо в глобальном пространстве имен. Они не могут ссылаться на window или document. Они не могут вызывать console.log или Math.max.

Рекомендации по использованию высказываний

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

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

вернуться к началу

Синтаксис привязки данных: Обзор

Привязка данных — это механизм согласования того, что видят пользователи, со значениями данных приложения. Хотя вы могли бы передавать и получать значения в HTML, приложение легче писать, читать и поддерживать, если вы передадите эти задачи в систему привязки. Вам просто нужно объявить привязки между источниками привязки и целевыми элементами HTML, а фреймворк сделает всю работу.

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

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

Направление данных Синтаксис Тип
Однонаправленное от источника данных к целевому элементу представления
{{expression}}
[target]="expression"
bind-target="expression"
Интерполяция Свойство Атрибут Класс Стиль
Однонаправленное от целевого элемента представления к источнику данных
(target)="statement"
on-target="statement"
Событие
Двунаправленное
[(target)]="expression"
bindon-target="expression"
Двунаправленная

У типов привязки, отличных от интерполяции, есть имя целевого объекта слева от знака равенства, заключенное в знаки препинания ([], ()) или предваряемое префиксом (bind-, on-, bindon-).

Имя целевого объекта — это имя свойства. Оно может выглядеть как имя атрибута, но им никогда не является. Чтобы понять разницу, вам нужно изменить свой способ мышления о шаблонах HTML.

Новая модель мышления

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

Это действительно HTML Plus. Но он значительно отличается от привычного вам HTML. Он требует новой модели мышления.

В обычном процессе разработки HTML вы создаёте визуальную структуру с помощью HTML-элементов и изменяете эти элементы, задавая атрибуты элементов со строковыми константами.

<div class="special">Mental Model</div>
<img src="images/hero.png">
<button disabled>Save</button>

Так же вы создаёте структуру и инициализируете значения атрибутов в шаблонах Angular.

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

<!-- Normal HTML -->
<div class="special">Mental Model</div>
<!-- Wow! A new element! -->
<hero-detail></hero-detail>

Это HTML Plus.

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

<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Save</button>

Вы вскоре столкнётесь с необычной нотацией в квадратных скобках. Погружаясь глубже, ваша интуиция подсказывает, что вы привязываетесь к атрибуту disabled кнопки и устанавливаете его в текущее значение свойства компонента isUnchanged.

Ваша интуиция ошибается! Ваша обычная модель мышления HTML вводит в заблуждение. На самом деле, как только вы начинаете привязку данных, вы больше не работаете с HTML-атрибутами. Вы не устанавливаете атрибуты. Вы устанавливаете свойства элементов DOM, компонентов и директив.

HTML-атрибут против свойства DOM

Различие между HTML-атрибутом и свойством DOM имеет решающее значение для понимания того, как работает привязка Angular.

Атрибуты определяются HTML. Свойства определяются DOM (Document Object Model).

  • Несколько HTML-атрибутов имеют 1:1 соответствие свойствам. id — один из таких примеров.

  • Некоторые HTML-атрибуты не имеют соответствующих свойств. colspan — один из таких примеров.

  • Некоторые свойства DOM не имеют соответствующих атрибутов. textContent — один из таких примеров.

  • Многие HTML-атрибуты, похоже, отображаются в свойства… но не так, как вы можете подумать!

Эта последняя категория запутывает до тех пор, пока вы не усвоите это общее правило:

Атрибуты инициализируют свойства DOM, а затем выполняют свою функцию. Значения свойств могут меняться; значения атрибутов — нет.

Например, при отрисовке браузером <input type="text" value="Bob"> создается соответствующий узел DOM со свойством value, инициализированным значением «Bob».

Когда пользователь вводит «Sally» в поле ввода, свойство элемента DOM value становится «Sally». Но HTML-атрибут value остаётся неизменным, как вы обнаружите, если спросите у элемента ввода об этом атрибуте: input.getAttribute('value') возвращает «Bob».

HTML-атрибут value определяет начальное значение; свойство DOM value — текущее значение.

Атрибут disabled — ещё один любопытный пример. Свойство disabled кнопки по умолчанию false, поэтому кнопка включена. При добавлении атрибута disabled его простое присутствие инициализирует свойство disabled кнопки значением true, поэтому кнопка становится отключенной.

Добавление и удаление атрибута disabled отключает и включает кнопку. Значение атрибута не имеет значения, поэтому вы не можете включить кнопку, написав <button disabled="false">Still Disabled</button>.

Установка свойства disabled кнопки (например, с помощью привязки Angular) отключает или включает кнопку. Значение свойства имеет значение.

HTML-атрибут и свойство DOM — это не одно и то же, даже если у них одинаковое имя.

Этот факт стоит повторить: Привязка шаблона работает со свойствами и событиями, а не с атрибутами.

Мир без атрибутов

В мире Angular единственная роль атрибутов заключается в инициализации состояния элементов и директив. Когда вы пишете привязку данных, вы работаете исключительно со свойствами и событиями целевого объекта. HTML-атрибуты фактически исчезают.

С этой моделью в голове, продолжим изучение целевых объектов привязки.

Целевые объекты привязки

Целевой объект привязки данных — это что-то в DOM. В зависимости от типа привязки, целевым объектом может быть свойство (элемента | компонента | директивы), событие (элемента | компонента | директивы) или (редко) имя атрибута. Следующая таблица обобщает это:

Тип Целевой объект Примеры
Свойство Свойство элемента Свойство компонента Свойство директивы
<img [src]="heroImageUrl">
<hero-detail [hero]="currentHero"></hero-detail>
<div [ngClass]="{special: isSpecial}"></div>
Событие Событие элемента Событие компонента Событие директивы
<button (click)="onSave()">Save</button>
<hero-detail (deleteRequest)="deleteHero()"></hero-detail>
<div (myClick)="clicked=$event" clickable>click me</div>
Двунаправленное Событие и свойство
<input [(ngModel)]="name">
Атрибут Атрибут (исключение)
<button [attr.aria-label]="help">help</button>
Класс class свойство
<div [class.special]="isSpecial">Special</div>
Стиль style свойство
<button [style.color]="isSpecial ? 'red' : 'green'">

С этим общим представлением вы готовы подробно изучить типы привязки.

вернуться к началу

Привязка свойства ( [свойство] )

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

Наиболее распространённая привязка свойства устанавливает свойство элемента в значение свойства компонента. Примером является привязка свойства src элемента изображения к свойству компонента heroImageUrl:

<img [src]="heroImageUrl">

Ещё один пример — отключение кнопки, когда компонент говорит, что он isUnchanged:

<button [disabled]="isUnchanged">Cancel is disabled</button>

Ещё один — установка свойства директивы:

<div [ngClass]="classes">[ngClass] binding to the classes property</div>

И ещё один — установка свойства модели пользовательского компонента (превосходный способ взаимодействия родительского и дочернего компонентов):

<hero-detail [hero]="currentHero"></hero-detail>

id="one-way-in-">Одностороннее взаимодействие

Люди часто описывают привязку свойств как одностороннюю привязку данных, потому что она передаёт значение в одном направлении, от свойства данных компонента к свойству целевого элемента.

Вы не можете использовать привязку свойств для извлечения значений из целевого элемента. Вы не можете привязаться к свойству целевого элемента для его чтения. Вы можете только установить его.

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

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

Если вам необходимо прочитать свойство целевого элемента или вызвать один из его методов, вам потребуется другой метод. См. справочник API для ViewChild и ContentChild.

id="binding-target">Целевой объект привязки

Свойство элемента в квадратных скобках определяет целевое свойство. Целевым свойством в следующем коде является свойство src элемента изображения.

<img [src]="heroImageUrl">

Некоторые предпочитают альтернативу с префиксом bind-, известную как каноническая форма:

<img bind-src="heroImageUrl">

Имя целевого объекта всегда является именем свойства, даже когда оно кажется именем чего-то другого. Вы видите src, и можете подумать, что это имя атрибута. Нет. Это имя свойства элемента изображения.

Хотя целевыми объектами чаще всего являются свойства элементов, Angular сначала проверяет, является ли имя свойством известной директивы, как в следующем примере:

<div [ngClass]="classes">[ngClass] binding to the classes property</div>

Технически Angular сопоставляет имя с директивой входным элементом, одним из имён свойств, перечисленных в массиве inputs директивы, или свойством, оформленным с помощью @Input(). Такие входные данные отображаются на собственные свойства директивы.

Если имя не соответствует свойству известной директивы или элемента, Angular сообщает об ошибке «неизвестная директива».

id="avoid-side-effects">Избегайте побочных эффектов

Как упоминалось ранее, вычисление выражения шаблона не должно иметь видимых побочных эффектов. Сам язык выражений делает всё возможное, чтобы обезопасить вас. Вы не можете присвоить значение чему-либо в выражении привязки свойства, а также использовать операторы инкремента и декремента.

Конечно, выражение может вызывать свойство или метод, имеющие побочные эффекты. Angular не может узнать об этом или остановить вас.

Выражение может вызвать что-то вроде getFoo(). Только вы знаете, что getFoo() делает. Если getFoo() изменяет что-либо, и вы привязаны к этому, вы рискуете неприятностями. Angular может или не может отобразить изменённое значение. Angular может обнаружить изменение и выдать предупреждающую ошибку. В общем, придерживайтесь свойств данных и методов, которые возвращают значения и не делают больше ничего.

id="return-the-proper-type">Возвращение правильного типа

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

Свойство hero компонента HeroDetail ожидает объект Hero, именно это вы и передаёте в привязке свойства:

<hero-detail [hero]="currentHero"></hero-detail>

id="remember-the-brackets">Не забывайте скобки

Скобки сообщают Angular, что необходимо вычислить выражение шаблона. Если вы опустите скобки, Angular рассматривает строку как константу и инициализирует целевое свойство этой строкой. Он не вычисляет строку!

Не делайте следующей ошибки:

<!-- ERROR: HeroDetailComponent.hero expects a
     Hero object, not the string "currentHero" -->
  <hero-detail hero="currentHero"></hero-detail>

id="one-time-initialization">Однократная инициализация строки

Вы должны опустить скобки, когда выполняются все следующие условия:

  • Целевое свойство принимает строковое значение.
  • Строка имеет фиксированное значение, которое можно встроить в шаблон.
  • Это начальное значение никогда не меняется.

Вы обычно инициализируете атрибуты таким образом в стандартном HTML, и это работает так же хорошо для инициализации свойства директивы и компонента. В следующем примере свойство prefix компонента HeroDetailComponent инициализируется фиксированной строкой, а не выражением шаблона. Angular устанавливает его и забывает о нём.

<hero-detail prefix="You are my" [hero]="currentHero"></hero-detail>

Привязка [hero], с другой стороны, остаётся живой привязкой к свойству currentHero компонента.

id="property-binding-or-interpolation-">Привязка свойства или интерполяция?

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

<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>

<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>

Интерполяция является удобной альтернативой привязке свойства во многих случаях.

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

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

id="content-security">Безопасность содержимого

Представьте следующее вредное содержимое.

evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';

К счастью, привязка данных Angular бдительно следит за опасным HTML. Он очищает значения перед их отображением. Он не позволит HTML с тегами сценария просочиться в браузер, ни с интерполяцией, ни с привязкой свойства.

<!--
  Angular generates warnings for these two lines as it sanitizes them
  WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
 -->
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>

Интерполяция обрабатывает теги сценария по-другому, чем привязка свойства, но оба подхода отображают содержимое безвредно.

evil title made safe

вернуться к началу

id="other-bindings">Привязки атрибутов, классов и стилей

Синтаксис шаблона предоставляет специализированные односторонние привязки для ситуаций, которые менее подходят для привязки свойств.

id="attribute-binding">Привязка атрибута

Вы можете установить значение атрибута напрямую с помощью привязки атрибута.

Это единственное исключение из правила, что привязка устанавливает целевое свойство. Это единственная привязка, которая создаёт и устанавливает атрибут.

В этом руководстве неоднократно подчеркивается, что установка свойства элемента с помощью привязки свойства всегда предпочтительнее установки атрибута с помощью строки. Зачем Angular предлагает привязку атрибута?

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

Рассмотрите атрибуты ARIA, SVG и span таблицы. Они являются чистыми атрибутами. Они не соответствуют свойствам элемента и не устанавливают свойства элементов. Нет целевых свойств для привязки.

Этот факт становится болезненно очевидным, когда вы пишете что-то вроде этого.

<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>

И получаете эту ошибку:

Template parse errors:
Can't bind to 'colspan' since it isn't a known native property

Как сказано в сообщении, элемент <td> не имеет свойства colspan. У него есть атрибут «colspan», но интерполяция и привязка свойства могут устанавливать только свойства, а не атрибуты.

Для создания и привязки к таким атрибутам нужны привязки атрибутов.

Синтаксис привязки атрибутов похож на синтаксис привязки свойств. Вместо свойства элемента в квадратных скобках используйте префикс attr, за которым следует точка (.) и имя атрибута. Затем вы устанавливаете значение атрибута, используя выражение, которое разрешается в строку.

Привяжите [attr.colspan] к рассчитанному значению:

<table border=1>
  <!--  expression calculates colspan=2 -->
  <tr><td [attr.colspan]="1 + 1">One-Two</td></tr>

  <!-- ERROR: There is no `colspan` property to set!
    <tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
  -->

  <tr><td>Five</td><td>Six</td></tr>
</table>

Вот как таблица отображается:

One-Two
Five Six

Одним из основных случаев использования привязки атрибутов является установка атрибутов ARIA, как в этом примере:

<!-- create and set an aria attribute for assistive technology -->
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
вернуться к началу

id="class-binding">Привязка класса

Вы можете добавлять и удалять имена классов CSS из атрибута class элемента с помощью привязки класса.

Синтаксис привязки класса похож на синтаксис привязки свойства. Вместо свойства элемента в квадратных скобках используйте префикс class, необязательно за которым следует точка (.) и имя CSS-класса: [class.class-name].

В следующих примерах показано, как добавить и удалить класс «special» приложения с помощью привязки класса. Вот как установить атрибут без привязки:

<!-- standard class attribute setting  -->
<div class="bad curly special">Bad curly special</div>

Вы можете заменить это привязкой к строке нужных имён классов; это полная или ничтожная привязка замена.

<!-- reset/override all class names with a binding  -->
<div class="bad curly special"
     [class]="badCurly">Bad curly</div>

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

<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>

<!-- binding to `class.special` trumps the class attribute -->
<div class="special"
     [class.special]="!isSpecial">This one is not so special</div>

Хотя это неплохой способ переключить одно имя класса, директива NgClass обычно предпочтительнее при одновременном управлении несколькими именами классов.

вернуться к началу

id="style-binding">Привязка стиля

Вы можете задавать встроенные стили с помощью привязки стиля.

Синтаксис привязки стиля похож на синтаксис привязки свойства. Вместо свойства элемента в квадратных скобках используйте префикс style, за которым следует точка (.) и имя свойства CSS: [style.style-property].

<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>

Некоторые стили привязки стиля имеют расширение единиц. В следующем примере условно устанавливается размер шрифта в единицах «em» и «%».

<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>

Хотя это неплохой способ установить один стиль, директива NgStyle обычно предпочтительнее при установке нескольких встроенных стилей одновременно.

Обратите внимание, что имя свойства стиля может быть записано как dash-case, как показано выше, или camelCase, например fontSize.

вернуться к началу

id="event-binding">Привязка событий ( (event) )

Привязки директивы, с которыми вы уже познакомились, передают данные в одном направлении: от компонента к элементу.

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

Единственный способ узнать о действии пользователя — прослушивать определённые события, такие как нажатия клавиш, движения мыши, щелчки и касания. Вы объявляете свой интерес к действиям пользователя с помощью привязки событий Angular.

Синтаксис привязки событий состоит из имени целевого события в скобках слева от знака равенства и цитируемого выражения шаблона справа. Следующая привязка событий слушает события щелчка кнопки, вызывая метод компонента onSave() всякий раз, когда происходит щелчок:

<button (click)="onSave()">Save</button>

Целевое событие

Имя в скобках — например, (click) — определяет целевое событие. В приведенном ниже примере целевым событием является щелчок кнопки.

<button (click)="onSave()">Save</button>

Некоторые люди предпочитают альтернативный префикс on-, известный как каноническая форма:

<button on-click="onSave()">On Save</button>

Элементные события могут быть более распространёнными целями, но Angular в первую очередь проверяет, соответствует ли имя свойству события известной директивы, как в следующем примере:

<!-- `myClick` is an event on the custom `ClickDirective` -->
<div (myClick)="clickMessage=$event" clickable>click with myClick</div>

Директива myClick более подробно описана в разделе алиасирования свойств ввода/вывода.

Если имя не соответствует элементу события или свойству вывода известной директивы, Angular сообщает об ошибке «неизвестная директива».

$event и операторы обработки событий

В привязке событий Angular устанавливает обработчик событий для целевого события.

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

Привязка передает информацию о событии, включая значения данных, через объект события с именем $event.

Форма объекта события определяется целевым событием. Если целевое событие является событием элемента DOM, то $event — это объект события DOM с такими свойствами, как target и target.value.

Рассмотрим этот пример:

<input [value]="currentHero.name"
       (input)="currentHero.name=$event.target.value" >

Этот код устанавливает свойство поля ввода value путём привязки к свойству name. Чтобы прослушивать изменения значения, код привязывается к событию поля ввода input. Когда пользователь вносит изменения, возникает событие input, и привязка выполняет оператор в контексте, который включает объект события DOM, $event.

Для обновления свойства name изменённый текст извлекается по пути $event.target.value.

Если событие относится к директиве (напоминаем, что компоненты являются директивами), $event имеет любую форму, которую директива решила произвести.

Пользовательские события с EventEmitter

Директивы обычно генерируют пользовательские события с помощью Angular EventEmitter. Директива создаёт EventEmitter и предоставляет его как свойство. Директива вызывает EventEmitter.emit(payload) для срабатывания события, передавая полезную нагрузку сообщения, которая может быть любой. Родительские директивы прослушивают событие, привязываясь к этому свойству и обращаясь к полезной нагрузке через объект $event.

Рассмотрим компонент HeroDetailComponent, который отображает информацию о героях и реагирует на действия пользователя. Хотя у компонента HeroDetailComponent есть кнопка удаления, он не знает, как удалить самого героя. Лучшее, что он может сделать, — это сгенерировать событие, сообщающее об запросе удаления пользователя.

Вот соответствующие фрагменты из этого компонента HeroDetailComponent:

src/app/hero-detail.component.ts (шаблон)

template: `
<div>
  <img src="{{heroImageUrl}}">
  <span [style.text-decoration]="lineThrough">
    {{prefix}} {{hero?.name}}
  </span>
  <button (click)="delete()">Delete</button>
</div>`

src/app/hero-detail.component.ts (deleteRequest)

// This component make a request but it can't actually delete a hero.
deleteRequest = new EventEmitter<Hero>();

delete() {
  this.deleteRequest.emit(this.hero);
}

Компонент определяет свойство deleteRequest, которое возвращает EventEmitter. Когда пользователь нажимает удалить, компонент вызывает метод delete(), сообщая EventEmitter о вызове события Hero.

Теперь представьте себе родительский компонент, который привязан к событию HeroDetailComponent deleteRequest.

<hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>

Когда событие deleteRequest срабатывает, Angular вызывает метод родительского компонента deleteHero, передавая героя на удаление (выпущенный HeroDetail) в переменной $event.

Операторы шаблона имеют побочные эффекты

Метод deleteHero имеет побочный эффект: он удаляет героя. Побочные эффекты операторов шаблона не только допустимы, но и ожидаются.

Удаление героя обновляет модель, что может вызвать другие изменения, включая запросы и сохранения на удалённом сервере. Эти изменения распространяются по системе и в конечном итоге отображаются в этом и других представлениях.

Вернуться к началу

Двухсторонняя привязка ( [(...)] )

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

На стороне элемента это сочетание задания определённого свойства элемента и прослушивания события изменения элемента.

Angular предлагает специальный синтаксис двухсторонней привязки данных для этой цели, [(x)]. Синтаксис [(x)] объединяет квадратные скобки привязки свойств, [x], со скобками привязки событий, (x).

[( )] = банан в коробке

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

Синтаксис [(x)] легко продемонстрировать, когда элемент имеет устанавливаемое свойство, называемое x, и соответствующее событие с именем xChange. Вот компонент SizerComponent, который соответствует шаблону. Он имеет свойство значения size и сопутствующее событие sizeChange:

src/app/sizer.component.ts

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

@Component({
  selector: 'my-sizer',
  template: `
  <div>
    <button (click)="dec()" title="smaller">-</button>
    <button (click)="inc()" title="bigger">+</button>
    <label [style.font-size.px]="size">FontSize: {{size}}px</label>
  </div>`
})
export class SizerComponent {
  @Input()  size: number | string;
  @Output() sizeChange = new EventEmitter<number>();

  dec() { this.resize(-1); }
  inc() { this.resize(+1); }

  resize(delta: number) {
    this.size = Math.min(40, Math.max(8, +this.size + delta));
    this.sizeChange.emit(this.size);
  }
}

Начальное значение size — это значение ввода из привязки свойств. Нажатие кнопок увеличивает или уменьшает size, в пределах ограничений min/max, а затем генерирует (вызывает) событие sizeChange с откорректированным размером.

Вот пример, в котором AppComponent.fontSizePx двухсторонне привязано к SizerComponent:

<my-sizer [(size)]="fontSizePx"></my-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>

AppComponent.fontSizePx устанавливает начальное значение SizerComponent.size. Нажатие кнопок обновляет AppComponent.fontSizePx через двухстороннюю привязку. Пересмотренное значение AppComponent.fontSizePx передаётся привязке стиля, делая отображаемый текст больше или меньше.

Синтаксис двухсторонней привязки на самом деле просто синтаксический сахар для привязки свойства и привязки события. Angular распаковывает привязку SizerComponent таким образом:

<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>

Переменная $event содержит полезную нагрузку события SizerComponent.sizeChange. Angular присваивает значение $event свойству AppComponent.fontSizePx при нажатии пользователем кнопок.

Очевидно, что синтаксис двухсторонней привязки очень удобен по сравнению с отдельными привязками свойства и события.

Было бы удобно использовать двухстороннюю привязку с элементами HTML-форм, такими как <input> и <select>. Однако ни один встроенный элемент HTML не следует шаблону значения x и события xChange.

К счастью, директива Angular NgModel — это мост, который позволяет двухстороннюю привязку к элементам форм.

Вернуться к началу

Встроенные директивы

В предыдущих версиях Angular было более семидесяти встроенных директив. Сообщество внесло ещё много, и бесчисленное количество частных директив было создано для внутренних приложений.

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

<button (click)="onSave()">Save</button>

Вы всё ещё получаете выгоду от директив, которые упрощают сложные задачи. Angular по-прежнему поставляется со встроенными директивами; просто не так много. Вы будете создавать свои собственные директивы, но не так много.

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

Встроенные атрибутивные директивы

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

Многие детали описаны в руководстве Атрибутивные Директивы. Многие модули Angular, такие как RouterModule и FormsModule, определяют свои собственные атрибутивные директивы. Этот раздел представляет наиболее часто используемые атрибутивные директивы:

  • NgClass - добавление и удаление набора CSS-классов
  • NgStyle - добавление и удаление набора HTML-стилей
  • NgModel - двухсторонняя привязка к элементу HTML-формы
Вернуться к началу

NgClass

Обычно вы управляете отображением элементов, добавляя и удаляя CSS-классы динамически. Вы можете привязать ngClass к объекту управления, чтобы добавить или удалить несколько классов одновременно.

Привязка к классу — хороший способ добавить или удалить один класс.

<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>

Чтобы добавить или удалить несколько CSS-классов одновременно, директива NgClass может быть лучшим вариантом.

Попробуйте привязать ngClass к объекту управления ключом: значением. Каждый ключ объекта — имя CSS-класса; его значение — true , если класс должен быть добавлен, false , если он должен быть удалён.

Рассмотрим метод компонента setCurrentClasses, который устанавливает свойство компонента, currentClasses, с объектом, который добавляет или удаляет три класса, в зависимости от состояния true/false трех других свойств компонента:

currentClasses: {};
setCurrentClasses() {
  // CSS classes: added/removed per current state of component properties
  this.currentClasses =  {
    saveable: this.canSave,
    modified: !this.isUnchanged,
    special:  this.isSpecial
  };
}

Добавление привязки свойства ngClass к currentClasses устанавливает классы элемента соответствующим образом:

<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>

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

вернуться к началу

NgStyle

Вы можете динамически установить инлайновые стили, основываясь на состоянии компонента. С помощью NgStyle вы можете одновременно установить множество инлайновых стилей.

Привязка стилей — это простой способ установить одно значение стиля.

<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
  This div is x-large or smaller.
</div>

Для одновременной установки многих инлайновых стилей директива NgStyle может быть более подходящим вариантом.

Попробуйте привязать ngStyle к объекту управления ключом:значение. Каждый ключ объекта — это имя стиля, а его значение — то, что нужно для этого стиля.

Рассмотрим метод компонента setCurrentStyles, который устанавливает свойство компонента, currentStyles, с объектом, определяющим три стиля, в зависимости от состояния трех других свойств компонента:

currentStyles: {};
setCurrentStyles() {
  // CSS styles: set per current state of component properties
  this.currentStyles = {
    'font-style':  this.canSave      ? 'italic' : 'normal',
    'font-weight': !this.isUnchanged ? 'bold'   : 'normal',
    'font-size':   this.isSpecial    ? '24px'   : '12px'
  };
}

Добавление привязки свойства ngStyle к currentStyles устанавливает стили элемента соответствующим образом:

<div [ngStyle]="currentStyles">
  This div is initially italic, normal weight, and extra large (24px).
</div>

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

вернуться к началу

NgModel - Двусторонняя привязка к элементам формы с [(ngModel)]

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

Двусторонняя привязка данных с директивой NgModel упрощает эту задачу. Вот пример:

<input [(ngModel)]="currentHero.name">

FormsModule необходим для использования ngModel

Перед использованием директивы ngModel в двусторонней привязке данных необходимо импортировать FormsModule и добавить её в список imports модуля Angular. Подробнее о FormsModule и ngModel можно узнать в руководстве по формам (Forms).

Вот как импортировать FormsModule для доступности [(ngModel)].

src/app/app.module.ts (FormsModule import)

import { NgModule } from '@angular/core';
import { BrowserModule }  from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; // <--- JavaScript import from Angular

/* Other imports */

@NgModule({
  imports: [
    BrowserModule,
    FormsModule  // <--- import into the NgModule
  ],
  /* Other module metadata */
})
export class AppModule { }

Внутри [(ngModel)]

Обращаясь к привязке name, обратите внимание, что вы могли бы добиться того же результата с отдельными привязками к свойству <input> элемента value и событию input.

<input [value]="currentHero.name"
       (input)="currentHero.name=$event.target.value" >

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

Директива ngModel скрывает эти неудобные детали за собственными свойствами ввода ngModel и вывода ngModelChange.

<input
  [ngModel]="currentHero.name"
  (ngModelChange)="currentHero.name=$event">

Свойство данных ngModel устанавливает свойство значения элемента, а свойство события ngModelChange прослушивает изменения значения элемента.

Подробности специфичны для каждого типа элемента, поэтому директива NgModel работает только для элемента, поддерживаемого ControlValueAccessor, который адаптирует элемент к этому протоколу. Поле ввода — один из таких элементов. Angular предоставляет обработчики значений для всех основных элементов HTML-формы, а руководство по формам (Forms) показывает, как к ним привязаться.

Директиву [(ngModel)] нельзя применить к неформенному нативному элементу или стороннему пользовательскому компоненту, пока вы не напишете подходящий обработчик значений — техника, выходящая за рамки данного руководства.

Обработчик значений не нужен для создаваемого вами Angular-компонента, поскольку вы можете назвать свойства значения и события в соответствии с базовой синтаксической конструкцией двусторонней привязки Angular (two-way binding syntax) и пропустить NgModel вообще. Пример этой техники — sizer показанный выше.

Отдельные привязки ngModel — это улучшение по сравнению с привязкой к нативным свойствам элемента. Можно сделать лучше.

Вам не нужно упоминать свойство данных дважды. Angular должен уметь захватить свойство данных компонента и установить его с помощью одной декларации, что он может сделать с синтаксисом [(ngModel)].

<input [(ngModel)]="currentHero.name">

Это [(ngModel)] все, что вам нужно? Есть ли когда-либо причина вернуться к его расширенной форме?

Синтаксис [(ngModel)] может только установить свойство, привязанное к данным. Если вам нужно сделать что-то большее или иное, вы можете написать расширенную форму.

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

<input
  [ngModel]="currentHero.name"
  (ngModelChange)="setUppercaseName($event)">

Вот все варианты в действии, включая вариант с верхним регистром:

NgModel variations

вернуться к началу

Встроенные директивы структуры

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

Подробное описание директив структуры содержится в руководстве Директивы структуры, где вы узнаете:

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

Эта часть — введение в распространённые директивы структуры:

  • NgIf — условно добавляет или удаляет элемент из DOM
  • NgFor — повторяет шаблон для каждого элемента в списке
  • NgSwitch — набор директив для переключения между альтернативными представлениями

NgIf

Вы можете добавить или удалить элемент из DOM, применив директиву NgIf к этому элементу (называемому элементом-хостом). Привяжите директиву к выражению условия, например, к isActive в этом примере.

<hero-detail *ngIf="isActive"></hero-detail>

Не забудьте звёздочку (*) перед ngIf.

Когда выражение isActive возвращает истинное значение, NgIf добавляет HeroDetailComponent в DOM. Когда выражение ложно, NgIf удаляет HeroDetailComponent из DOM, уничтожая этот компонент и все его подкомпоненты.

Показать/скрыть — это не одно и то же

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

<!-- isSpecial is true -->
<div [class.hidden]="!isSpecial">Show with class</div>
<div [class.hidden]="isSpecial">Hide with class</div>

<!-- HeroDetail is in the DOM but hidden -->
<hero-detail [class.hidden]="isSpecial"></hero-detail>

<div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
<div [style.display]="isSpecial ? 'none'  : 'block'">Hide with style</div>

Скрытие элемента сильно отличается от удаления элемента с помощью NgIf.

При скрытии элемента этот элемент и все его потомки остаются в DOM. Все компоненты этих элементов остаются в памяти, и Angular может продолжать проверять изменения. Вы можете удерживать значительные вычислительные ресурсы и снижать производительность для чего-то, что пользователь не видит.

Когда NgIf — false, Angular удаляет элемент и его потомков из DOM. Он уничтожает их компоненты, потенциально высвобождая значительные ресурсы, что приводит к более отзывчивому пользовательскому опыту.

Метод «показать/скрыть» подходит для нескольких элементов с небольшим числом дочерних элементов. Следует проявлять осторожность при скрытии больших деревьев компонентов; NgIf может быть более безопасным выбором.

Защита от нулевого значения

Директива ngIf часто используется для защиты от null. «Показать/скрыть» бесполезно в качестве защиты. Angular выбросит ошибку, если вложенное выражение попытается получить доступ к свойству null.

Здесь мы видим NgIf , защищающие два <div> . Имя currentHero будет отображаться только при наличии currentHero . nullHero никогда не будет отображаться.

<div *ngIf="currentHero">Hello, {{currentHero.name}}</div>
<div *ngIf="nullHero">Hello, {{nullHero.name}}</div>

Также см. оператор безопасной навигации, описанный ниже.

вернуться к началу

NgFor

NgFor — это директива повторителя — способ представления списка элементов. Вы определяете блок HTML, который определяет, как должен отображаться один элемент. Вы говорите Angular использовать этот блок в качестве шаблона для рендеринга каждого элемента в списке.

Вот пример применения NgFor к простому <div>:

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

Вы также можете применить NgFor к элементу компонента, как в этом примере:

<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>

Не забудьте звёздочку (*) перед ngFor.

Текст, присвоенный *ngFor , — это инструкция, которая направляет процесс повторения.

*ngFor microsyntax

Строка, присвоенная *ngFor , не является выражением шаблона. Это микросинтаксис — собственный небольшой язык, который интерпретирует Angular. Строка "let hero of heroes" означает:

Возьмите каждого героя в массиве heroes, сохраните его в локальной переменной цикла hero и сделайте его доступным для HTML шаблона на каждой итерации.

Angular переводит эту инструкцию в <template> вокруг элемента-хоста, а затем использует этот шаблон многократно для создания нового набора элементов и привязок для каждого hero в списке.

Узнайте о микросинтаксисе в руководстве по структурным директивам.

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

Ключевое слово let перед hero создаёт переменную шаблона-ввода, называемую hero. Директива ngFor итерируется по массиву heroes, возвращаемому свойством родительского компонента heroes, и устанавливает hero на текущий элемент из массива во время каждой итерации.

Вы ссылаетесь на переменную ввода hero внутри элемента-хоста ngFor (и внутри его потомков) для доступа к свойствам героя. Здесь она ссылается сначала в интерполяции, а затем передаётся в привязку к свойству hero компонента <hero-detail>.

<div *ngFor="let hero of heroes">{{hero.name}}</div>
<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>

Узнайте больше о переменных шаблона-ввода в руководстве по структурным директивам.

*ngFor с индексом

Свойство index контекста директивы NgFor возвращает нулевой индекс элемента во время каждой итерации. Вы можете захватить index в переменной шаблона-ввода и использовать её в шаблоне.

В следующем примере захватывается index в переменную с именем i и отображается вместе с именем героя так:

<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.name}}</div>

Узнайте о других значениях контекста NgFor, таких как last, even, и odd в справочнике по API NgFor.

*ngFor с trackBy

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

Например, повторный запрос к серверу мог бы обновить список всеми новыми объектами героев.

Большинство, если не все, это ранее отображённые герои. Вы это знаете, потому что id каждого героя не изменились. Но Angular видит только новый список новых ссылок на объекты. Он не имеет иного выбора, кроме как разрушить старые элементы DOM и вставить все новые элементы DOM.

Angular может избежать этой работы с trackBy. Добавьте метод в компонент, который возвращает значение NgFor должен отслеживать. В этом случае это значение - id героя.

trackByHeroes(index: number, hero: Hero): number { return hero.id; }

В выражении микросинтаксиса установите trackBy на этот метод.

<div *ngFor="let hero of heroes; trackBy: trackByHeroes">
  ({{hero.id}}) {{hero.name}}
</div>

Вот иллюстрация эффекта trackBy. «Сбросить героев» создаёт новых героев с теми же hero.id. «Изменить идентификаторы» создаёт новых героев с новыми hero.id.

  • Без trackBy, обе кнопки вызывают полную замену элементов DOM.
  • С trackBy, только изменение id вызывает замену элементов.
trackBy

назад к началу

Директивы NgSwitch

NgSwitch похож на оператор JavaScript switch. Он может отобразить один элемент из нескольких возможных элементов на основе условия переключения. Angular помещает только выбранный элемент в DOM.

NgSwitch фактически представляет собой набор из трёх взаимодействующих директив: NgSwitch, NgSwitchCase, и NgSwitchDefault, как показано в этом примере.

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

NgSwitch - это управляющая директива. Привяжите её к выражению, которое возвращает значение переключения. Значение emotion в этом примере - строка, но значение переключения может быть любого типа.

Привяжите к [ngSwitch]. Вы получите ошибку, если попытаетесь установить *ngSwitch, потому что NgSwitch - это директива атрибута, а не структурная директива. Она изменяет поведение своих сопутствующих директив. Она не непосредственно взаимодействует с DOM.

Привяжите к *ngSwitchCase и *ngSwitchDefault. Директивы NgSwitchCase и NgSwitchDefault являются структурными директивами, поскольку они добавляют или удаляют элементы из DOM.

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

Директивы переключения особенно полезны для добавления и удаления элементов компонента. Этот пример переключается между четырьмя компонентами "эмоциональных героев", определённых в файле hero-switch.components.ts. Каждый компонент имеет свойство ввода hero, которое связано со свойством currentHero родительского компонента.

Директивы переключения также работают с обычными элементами и веб-компонентами. Например, вы могли бы заменить случай <confused-hero> переключения следующим:

<div *ngSwitchCase="'confused'">Are you as confused as {{currentHero.name}}?</div>
назад к началу

Переменные шаблона-ссылок ( #var )

Переменная шаблона-ссылок часто является ссылкой на элемент DOM в шаблоне. Она также может быть ссылкой на компонент или директиву Angular или веб-компонент.

Используйте символ решётки (#), чтобы объявить переменную ссылки. #phone объявляет переменную phone для элемента <input>.

<input #phone placeholder="phone number">

Вы можете ссылаться на переменную шаблона-ссылок везде в шаблоне. Переменная phone , объявленная в этом <input>, используется в <button> на другой стороне шаблона.

<input #phone placeholder="phone number">

<!-- lots of other elements -->

<!-- phone refers to the input element; pass its `value` to an event handler -->
<button (click)="callPhone(phone.value)">Call</button>

Как переменная ссылки получает своё значение

В большинстве случаев Angular устанавливает значение переменной ссылки на элемент, в котором она была объявлена. В предыдущем примере phone ссылается на поле <input> "номер телефона". Обработчик нажатия кнопки телефона передаёт значение ввода в метод компонента callPhone. Но директива может изменить это поведение и установить значение на что-то другое, например, на себя. Директива NgForm делает именно это.

Ниже приведён упрощённый вариант примера формы из руководства по формам.

<form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">
  <div class="form-group">
    <label for="name">Name
      <input class="form-control" name="name" required [(ngModel)]="hero.name">
    </label>
  </div>
  <button type="submit" [disabled]="!heroForm.form.valid">Submit</button>
</form>
<div [hidden]="!heroForm.form.valid">
  {{submitMessage}}
</div>

Переменная шаблона-ссылок heroForm появляется трижды в этом примере, разделённые большим количеством HTML. Каково значение heroForm?

Если Angular не принял его, когда вы импортировали FormsModule, это был бы HTMLFormElement. На самом деле heroForm является ссылкой на директиву Angular NgForm с возможностью отслеживать значение и валидность каждого элемента управления в форме.

У обычного элемента <form> нет свойства form. Но директива NgForm имеет это свойство, что объясняет, как вы можете отключить кнопку отправки, если форма heroForm.form.valid недействительна, и передать всю структуру управления формы в метод родительского компонента onSubmit.

Примечания к предупреждениям по переменным шаблона-ссылок

Переменная шаблона ссылки (#phone) не такая же, как переменная шаблона ввода (let phone), например, в *ngFor. Узнайте об этом различии в руководстве по структурным директивам.

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

Вы можете использовать префикс ref- в качестве альтернативы #. В этом примере переменная fax объявляется как ref-fax, а не как #fax.

<input ref-fax placeholder="fax number">
<button (click)="callFax(fax.value)">Fax</button>
назад к началу

Свойства ввода и вывода ( @Input и @Output )

До сих пор вы в основном работали с привязкой к членам компонента внутри выражений и операторов шаблона, которые находятся с правой стороны оператора привязки. Член в этой позиции является источником привязки данных.

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

Помните: все компоненты являются директивами.

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

Целью привязки является свойство или событие внутри знака препинания привязки: =, = или []. Источник находится либо в кавычках (" " ), либо в интерполяции ({{}}).

Каждый член источника директивы автоматически доступен для привязки. Вам ничего не нужно делать для доступа к члену директивы в выражении или операторе шаблона.

Доступ к членам цели директивы ограничен. Вы можете привязываться только к свойствам, явно идентифицированным как входы и выходы.

В следующем фрагменте кода iconUrl и onSave являются членами привязки данных для AppComponent и ссылаются внутри кавычек справа от знака равенства (=).

<img [src]="iconUrl"/>
<button (click)="onSave()">Save</button>

Они являются ни входами, ни выходами компонента. Они являются источниками для их связей. Целевые элементы — это родные <img> и <button> элементы.

Теперь посмотрите на другой фрагмент, в котором HeroDetailComponent является целевым элементом связи слева от знака равенства (=).

<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
</hero-detail>

Оба HeroDetailComponent.hero и HeroDetailComponent.deleteRequest находятся на левой стороне объявлений связей. HeroDetailComponent.hero находится в скобках; это цель привязки свойства. HeroDetailComponent.deleteRequest находится в скобках; это цель привязки события.

Объявление входных и выходных свойств

Целевые свойства должны быть явно помечены как входные или выходные.

В HeroDetailComponent, такие свойства помечаются как входные или выходные свойства с помощью декораторов.

@Input()  hero: Hero;
@Output() deleteRequest = new EventEmitter<Hero>();

В качестве альтернативы вы можете определить члены в массивах inputs и outputs метаданных директивы, как в этом примере:

@Component({
  inputs: ['hero'],
  outputs: ['deleteRequest'],
})

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

Входное или выходное?

Свойства входа обычно получают значения данных. Свойства выхода предоставляют производители событий, такие как объекты EventEmitter.

Термины вход и выход отражают перспективу целевой директивы.

Inputs and outputs

HeroDetailComponent.hero является входным свойством с точки зрения HeroDetailComponent, потому что данные поступают в это свойство из выражения привязки шаблона.

HeroDetailComponent.deleteRequest является выходным свойством с точки зрения HeroDetailComponent, потому что события поступают из этого свойства к обработчику в инструкции привязки шаблона.

Использование псевдонимов для входных/выходных свойств

Иногда общее имя входного/выходного свойства должно отличаться от внутреннего имени.

Это часто случается с директивами атрибутов. Потребители директивы ожидают привязки к имени директивы. Например, когда вы применяете директиву с селектором myClick к тегу <div>, вы ожидаете привязки к свойству события, которое также называется myClick.

<div (myClick)="clickMessage=$event" clickable>click with myClick</div>

Однако имя директивы часто является плохим выбором для имени свойства внутри класса директивы. Имя директивы редко описывает, что делает свойство. Имя директивы myClick не является хорошим именем для свойства, которое излучает сообщения о кликах.

К счастью, вы можете иметь общее имя свойства, которое соответствует обычным ожиданиям, используя другое имя внутри. В приведенном выше примере вы фактически привязываетесь через псевдоним myClick к собственному свойству директивы clicks.

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

@Output('myClick') clicks = new EventEmitter<string>(); //  @Output(alias) propertyName = ...

Вы также можете использовать псевдонимы для имён свойств в массивах inputs и outputs. Вы пишете строку с двоеточием (:) с именем свойства директивы слева и общественным псевдонимом справа:

@Directive({
  outputs: ['clicks:myClick']  // propertyName:alias
})
Вернуться к началу

Операторы выражений шаблона

Язык выражений шаблонов использует подмножество синтаксиса JavaScript, дополненное несколькими специальными операторами для конкретных сценариев. В следующих разделах рассматриваются два из этих операторов: pipe и safe navigation operator.

Оператор pipe ( | )

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

Angular pipes — это хороший выбор для небольших преобразований, таких как эти. Pipes — это простые функции, которые принимают входное значение и возвращают преобразованное значение. Их легко применять в выражениях шаблонов, используя оператор pipe (|):

<div>Title through uppercase pipe: {{title | uppercase}}</div>

Оператор pipe передает результат выражения слева в функцию pipe справа.

Вы можете объединять выражения через несколько pipe:

<!-- Pipe chaining: convert title to uppercase, then to lowercase -->
<div>
  Title through a pipe chain:
  {{title | uppercase | lowercase}}
</div>

И вы также можете применять параметры к pipe:

<!-- pipe with configuration argument => "February 25, 1970" -->
<div>Birthdate: {{currentHero?.birthdate | date:'longDate'}}</div>

Pipe json особенно полезен для отладки привязок:

<div>{{currentHero | json}}</div>

Сгенерированный вывод будет выглядеть примерно так

{ "id": 0, "name": "Hercules", "emotion": "happy",
  "birthdate": "1970-02-25T08:00:00.000Z",
  "url": "http://www.imdb.com/title/tt0065832/",
  "rate": 325 }
Вернуться к началу

Оператор безопасной навигации ( ?. ) и пути свойств с нулевыми значениями

Оператор безопасной навигации Angular (?.) — это удобный и лаконичный способ защиты от нулевых и неопределённых значений в путях свойств. Вот он, защищающий от сбоя рендеринга представления, если currentHero равно null.

The current hero's name is {{currentHero?.name}}

Что произойдёт, если связанное свойство title имеет значение null?

The title is {{title}}

Представление всё ещё рендерится, но отображаемое значение пустое; вы видите только «Название —», и ничего после. Это разумное поведение. По крайней мере, приложение не аварийно завершается.

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

The null hero's name is {{nullHero.name}}

JavaScript генерирует ошибку обращения к null, и Angular также:

TypeError: Cannot read property 'name' of null in [null].

Хуже того, весь вид исчезает.

Это было бы разумным поведением, если бы свойство hero никогда не могло иметь значение null. Если это никогда не должно иметь значение null, а оно всё же имеет, это ошибка программирования, которую следует найти и исправить. Выброс исключения — правильное действие.

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

В ожидании данных представление должно рендериться без ошибок, и нулевой путь свойства должен отображаться пустым, как и свойство title.

К сожалению, приложение аварийно завершается, когда currentHero равно null.

Вы могли бы обойти эту проблему с помощью *ngIf.

<!--No hero, div not displayed, no error -->
<div *ngIf="nullHero">The null hero's name is {{nullHero.name}}</div>

Вы могли бы попытаться объединить части пути свойства с &&, зная, что выражение завершается, когда сталкивается с первым null.

The null hero's name is {{nullHero && nullHero.name}}

Эти подходы заслуживают внимания, но могут быть громоздкими, особенно если путь свойства длинный. Представьте себе защиту от null где-то в длинном пути свойства, например, в a.b.c.d.

Оператор безопасной навигации Angular (?.) — более удобный и лаконичный способ защиты от null в путях свойств. Выражение завершается, когда встречает первое нулевое значение. Отображение пустое, но приложение продолжает работать без ошибок.

<!-- No hero, no problem! -->
The null hero's name is {{nullHero?.name}}

Он отлично работает с длинными путями свойств, такими как a?.b?.c?.d.

Вернуться к началу

Заключение

Вы завершили этот обзор синтаксиса шаблонов. Теперь пришло время применить эти знания к своим компонентам и директивам.

Следующий шаг

Справочник

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

Spec-Zone.ru

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