Розбираємося з синтаксисом шаблонів в Angular2


Багато хто вперше побачивши синтаксис шаблонів Angular2 починають голосити, мовляв жах який зробили, невже не можна було як у Angular1 хоча-б. Навіщо потрібно було вводити це різноманітність скобочек, зірочок та іншої нісенітниці! Однак при найближчому розгляді все стає набагато простіше, головне не лякатися.

Так як шаблони AngularJS є невід'ємною його частиною, важливо розібратися з ними в самому початку знайомства з новою версією цього фреймворку. Заодно обговоримо, які переваги дає нам даний синтаксис порівняно з angular 1.x. Причому краще всього буде розглядати це на невеликих прикладах.

Дана стаття в чому заснована на матеріалах цих двох статей:



Для того, що б спростити подачу матеріалу, давайте розберемося. Під AngularJS я буду розуміти всю гілку Angular 1.x, в той час як під Angular2 — гілку 2.x.

Примітка: вечір вихідного дня, тому про опечатки і т. д. повідомляйте в лічку. Пребагато вдячний і приємного читання.


Биндинг властивостей елементів
У разі простого виведення даних різниці ви не відчуєте, однак, якщо ви вирішите передати частину статків в якості значення будь-яких атрибутів елементів, можна вже спостерігати цікаві рішення.

Давайте освіжимо в пам'яті як працюють биндинги атрибути AngularJS. Для цього ми зазвичай безпосередньо пробрасывали виразу (интерполируемые при компіляції шаблону) прямо в атрибут елемента, або користувалися однією з безлічі директив. Наприклад
ngValue
для встановлення значення властивості
value
у инпутов.

Наведемо приклад як це працює в AngularJS:

<input ng-value="expression" />

Так само не забуваємо що ми можемо просто інтерполювати результат виразу безпосередньо в якості аргументу:

<input value="{{expression}}" />

Зауважимо цікаву особливість. Другий варіант багато його уникають, так як можна побачити проміжне стан до того, як angular інтерполює значення. Однак перший варіант використовує директиви. Тобто для того що б у нас все було добре, красиво і зручно, нам треба зробити по директиві на кожне властивість всіх елементів. Погодьтеся, не дуже зручно. Чому б не додати якесь позначення для атрибута, яке б говорило ангуляру замэпить значення на нього. Причому було б непогано-що синтаксис був валідним. І вони додали, тепер для цього треба всього-лише обернути нас цікавить атрибут будь-атрибут) в квадратні дужки —
[]
.

<input [value]="valueExpression" [placeholder]="placeholderExpression" />

Однак, у нас все ще є можливість мэпить интерполируемое значення, так само як це було в AngularJS:

<input value="{{ valueExpression }}" placeholder="{{ placeholderExpression }}" />

Події
У AngularJS ми могли підписатися на події елементів використовуючи спеціальні директиви. Так само, як і у випадку з властивостями, нам доводиться мати справу з цілою купою можливих подій. І на кожну подію доводилося робити директиву. Мабуть, найпопулярнішою з таких директив є
ngClick
:

<button ng-click="doSomething($event)">

Враховуючи що ми вже вирішили таку проблему властивостей елементів, то напевно так само варто вирішувати і цю проблему? Саме це і зробили! Для того, щоб підписатися на подію, достатньо прописати атрибут используюя наступний синтаксис:
(eventName)
. Таким чином у нас є можливість підписатися на будь-яку подію генерується нашим елементом, при цьому без потреби писати окрему директиву:

<button (click)="doSomething($event)">

Двосторонній биндинг
Існує поширена думка, що двосторонній биндинг це погано, і що це основний гріх ангуляра. Це не зовсім так. Проблема з двостороннім биндингом в AnugularJS була в тому, що він використовується повсюдно, не даючи розробникам альтернативи (можливо ситуація з цим незабаром зміниться).

Все ж таки іноді виникають випадки коли двосторонній биндинг здорово спрощує розробку, особливо у разі форм. Так як же реалізований оний в Angular2? Давайте подумаємо, як організувати двосторонній биндинг маючи односторонній биндинг власти елементів і биндинг подій:

<input type="text" [value]="firstName" (input)="firstName=$event.target.value" />

Знову ж таки, не дуже зручно. Тому в Angular2 так само є синтаксичний цукор з використанням
ngModel
. Результат буде ідентичний тому, що ми привели вище:

<input type="text" [(ngModel)]="firstName" />

Локальні змінні
Для передачі даних між елементами в межах одного шаблону використовуються локальні змінні. Найбільш близькою аналогією в AngularJS, мабуть, можна вважати доступ до елементів форми по імені через ngForm. Звичайно, це не зовсім коректне порівняння, так як працює це тільки за рахунок директиви
ngForm
. У Angular2 ви можете використовувати посилання на будь-який об'єкт або DOM елемент в межах елемента шаблону та його нащадків, використовуючи локальні змінні
#
.

<video #movieplayer ...>
<button (click)="movieplayer.play()">
</video>

У даному прикладі ми можемо бачити, як через змінну
movieplayer
ми можемо отримати доступ до API медіа елементи прямо в шаблоні.

Крім символу
#
, ви так само можете оголошувати змінні використовуючи префікс
var-
. У цьому випадку замість
#movieplayer
ми могли б записати
var-movieplayer
.

Зірочка (символ *)
Символ
*
викликає найбільше запитань. Давайте розберемося, навіщо він знадобився. Для усвідомлення причин додавання цього символу, нам варто згадати про такому елементі
template
.

Елемент
template
дозволяє нам задекларувати шматочок DOM, який ми можемо ініціалізувати пізніше, що дає нам більш високу продуктивність і більш раціональне використання ресурсів. Чимось це схоже на
documentFragment
в контексті HTML.

Мабуть, буде простіше показати навіщо воно треба на прикладі:

<div style="display:none">
<img src="path/to/your/image.png" />
</div>

У цьому невеликому прикладі ми можемо бачити, що блок прихований (
display:none
). Однак браузер все одно буде намагатися завантажити картинку, навіть якщо вона не знадобиться. Якщо подібних речей на сторінці багато, це може згубно позначитися на загальній продуктивності сторінки.

Рішенням цієї проблеми стане використання елемента
template
.

<template>
<img src="path/to/your/image.png" />
</template>

В цьому випадку браузер не буде завантажувати зображення, поки ми не ініціалізуємо шаблон.

Але повернемося до наших баранів. Використання символу
*
перед директивою елемента дозволить ангуляру при компіляції обернути елемент шаблон. Простіше подивитися на прикладі:

<hero-detail *ngIf="isActive" [hero]="currentHero"></hero-detail>

Цей шаблон буде трансформовано у:

<template [ngIf]="isActive">
<hero-detail [hero]="currentHero"></hero-detail>
</template>

Тепер повинно стати ясно, що цей символ надає синтаксичний цукор для досягнення більш високої продуктивності при використанні умовних директив на зразок
ngFor
,
ngIf
та
ngSwitch
. Логічно що немає потреби у створенні екземпляра компоненту
hero-detail
поки
isActive
не є істиною.

Пайпи
Пайпи — це прямий аналог фільтрів з AngularJS. У загальному і цілому синтаксис їх застосування не особливо змінився:

<p>My birthday is {{ birthday | date:"MM/dd/yy" }} </p>

Навіщо знадобилося міняти назву з уже звичних фільтрів на нові пайпи — окреме питання. Зроблено це було б підкреслити нову механіку роботи фільтрів. Тепер це не синхронні фільтри, а асинхронні пайпи (за аналогією з unix pipes).

У AngularJS фільтри запускаються синхронно на кожен $digest цикл. Цього вимагає механізм відстеження змін в AngularJs. У Angular2 ж відстеження змін враховує залежності даних, тому це дозволяє оптимізувати цілий ряд концепцій. Так само з'явився поділ на stateful і stateless пайпи (в той час як фильры AngularJS завідомо вважалися stateful).

Stateless пайпи, як це може бути зрозуміло з назви, не мають власного стану. Це чисті функції. Вони виконуються тільки один раз (або якщо змінилися вхідні дані). Більшість пайпов в Angular2 є stateless пайпа. Це дозволяє істотно збільшити продуктивність.

Stateful пайпи навпаки, мають свій стан і вони часто виконуються в зв'язку з тим, що внутрішньо стан може змінитися. Прикладом подібного пайпа є
Async
. Він отримує на вхід промис, підписується на зміни та повертає заресолвленное значення.

// це не TypeScript, це babel з stage-1, ну так, до відома
@Component({
selector: 'my-hero',
template: 'Message: {{delayedMessage | async}}',
})
class MyHeroAsyncMessageComponent {
delayedMessage = new Promise((resolve, reject) => {
setTimeout(() => resolve('You are my Hero!'), 500);
});
}
// повелися? Нєє, це просто TypeScript без визначення типів.

У цьому прикладі компонент
my-hero
виведе
Message: You are my Hero!
тільки після того, як буде заресолвлен промис
delayedMessage
.

Для того що б зробити stateful пайпи ми повинні оголосити явно це метаданих оного. Інакше Angular2 буде вважати його stateless.

Elvis оператор
У AngularJS ми могли робити звернення до чого завгодно абсолютно безболісно, що часто виливалося в дуже підступні баги і ускладнювало налагодження. У Angular2 ми нарешті будемо отримувати помилки! Однак подібне рішення не всім може припасти до душі без додаткового цукру.

В Javascript нам частенько доводиться перевіряти наявність будь-яких властивостей. Думаю всі ми писали щось подібне:

if (cordova && cordova.plugins && cordova.plugins.notification){
// use cordova.plugins.notification
}

Роблячи такі перевірки ми звичайно ж хочемо уникнути подібного:

TypeError: Cannot read property 'notification' of undefined.

У coffescript для вирішення цієї проблеми був введений Elvis оператор. У Angular2 вирішили цю проблему використовуючи той же оператор, але на рівні шаблонів:

<p>Employer: {{employer?.companyName}}</p>

Даний запис означає, що властивість
employer
опціонально, і якщо воно має пусте значення, то інша частина виразу ігнорується. Якби ми не скористалися цим оператором, то в цій ситуації ми б отримали
TypeError
.

Так само як і в coffescript цей оператор можна використовувати скільки завгодно разів в рамках одного виразу, наприклад так:
a?.b?.c?.d


Висновки
Розробники Angular2 проробили величезну роботу для того що б зробити синтаксис шаблонів більш гнучким і потужним. Так, деякі речі потребують небагато часу для адаптації, але в цілому з часом все це здається більш ніж природним. А головне, не так все страшно як здається на перший погляд.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.