- PVSM.RU - https://www.pvsm.ru -
Не так давно в мире web-разработки произошло важное событие — вышла бета Angular 2 [1]. И уже можно строить предположения о том, как он будет выглядеть после релиза.
Но оценка сама по себе, в вакууме, напоминает выбор электроники по рекламным буклетам производителя. Все фишки, которые есть в гаджете, упомянуты. А все, которых нет — не упомянуты и мы можем про их существование даже не задуматься. Поэтому гораздо эффективнее оценивать сравнивая с чем-то еще.
Так и родилась мысль сравнить Angular 2 с новым, но весьма амбициозным проектом Aurelia [2], который так же недавно вышел в бету. А заодно пополнить копилку Хабра информацией об этом фреймворке, поскольку пока ее гораздо меньше, чем информации об Angular 2.
Код примеров из статьи в виде готовых для запуска проектов выложен на github [3]. Примеры написаны на TypeScript и оба используют systemjs [4]. Конфигурации systemjs были взяты из quick start guide для каждого фреймворка и достаточно сильно отличаются. Мне показалось разумным оставить их в том виде, в котором их предоставили авторы фреймворков, и не пытаться сделать похожими. Также отмечу, что используемый в проекте http-server [5] не поддерживает pushState (надеюсь, скоро будет поддерживать, ибо одобренный pull request есть [6]), и поэтому в Angular пришлось включить hash based routing.
Мы не будем сравнивать досконально каждую архитектурную особенность, а скорее оценим фреймворки с точки зрения конечного пользователя, который просто хочет понять какие плюшки он получит. Сравнить все в одной статье не получится, поэтому данная статья будет своего рода знакомством с общей структурой. А более сложные вопросы останутся на вторую часть.
Мы пойдем по следующему сценарию:
Поскольку на том же Хабре информации по Aurelia совсем немного [7], можно предположить, что не каждый читатель слышал о ней. Поэтому расскажу предысторию создания Aurelia. Тем более, что она довольно интересна и даже немного драматична.
Aurelia это проект Роба Эйзенберга [8], автора весьма популярного MV*-фреймворка для XAML-платформ Caliburn.Micro [9]. Позже он разработал MV*-фреймворк для web, получивший название Durandal [10]. Durandal не стал супер популярным, но, тем не менее, в нем были очень интересные и элегантные решения и фреймворк собрал свою аудиторию приверженцев, которые его очень полюбили.
Но Роб Эйзенберг понимал все недостатки Durandal, поэтому вместе с его сопровождением занимался разработкой так называемого NextGen фреймворка [11].
В январе 2014 года, на конференции ngConf небезызвестный в мире веб-разработки John Papa [12] поделился с менеджером Angular team Брэдом Грином [13] идеями, которые были заложены Робом Эйзенбергом в Durandal и в NextGen framework. Эти идеи заинтересовали Грина и он решил пообщаться с Эйзенбергом.
Встретившись, Брэд Грин и Роб Эйзенберг поняли, что их взгляды на будущее веба и веб-разработки во многом совпадают, и они приняли решение объединить усилия и Эйзенберг начал работать в команде Angular [14] над второй версией фреймворка.
Однако, спустя десять месяцев, он принял решение покинуть команду Angular [15], поскольку направление развития Angular 2, по его мнению, слишком сильно изменилось и отличалось от того Angular 2, на работу с которым он соглашался.
Эйзенберг за короткий срок собрал достаточно большую команду, в составе которой есть такие звезды как, например, Scott Allen [16], и вернулся к работе над фреймворком своей мечты. Итогом этой работы и стал Aurelia [17].
Общественность приняла фреймворк с интересом (как простейший способ оценки, на момент написания статьи Aurelia собрала на github 5000 звезд [18] против 8000 у Angular 2 [19]).
Общие принципы, заложенные в Angular 2 и Aurelia, очень похожи — это игроки одного класса. Но видение в деталях и набор возможностей у них достаточно сильно отличаются, что и делает их сравнение интересным.
Из осязаемых характеристик, которые можно сравнить при выборе фреймворка, посмотрим на perfomance. Aurelia [20] показывает интересные результаты в бенчмарке dbmonster, выбивая чуть лучшие баллы, чем Angular 2 [21], и заметно лучшие, чем React [22] и Angular1 [23].
Для того, чтобы все индикаторы работали корректно и для получения наиболее «чистого» результата, рекомендуется использовать браузер chrome и запускать его следующей командой:
"C:Program Files (x86)GoogleChromeApplicationchrome.exe" --user-data-dir="C:chromedev-sessionsperf" --enable-precise-memory-info --enable-benchmarking --js-flags="--expose-gc"
Из неосязаемых характеристик, попробую оценить самые сильные и слабые стороны обоих фреймворков (осторожно, ИМХО автора).
Архитектуры Angular 2 и Aurelia очень похожи. Ниже попытаюсь в пару абзацев сформулировать принципы работы обоих, обозначая курсивом основные термины и конструкции, которые имеет смысл рассматривать и сравнивать. Надеюсь, это получится читаемым текстом, а не месивом из терминов.
Основу приложения как на Angular 2, так и на Aurelia составляют компоненты, ассоциированные с соответствующим шаблоном.
Обязательно наличие root-компонента, который олицетворяет собой приложение (app). К компонентам могут/должны быть привязаны метаданные при помощи декораторов.
Инициализация компонентов выполняется при помощи dependency injection. Также, каждый компонент имеет декларированный жизненный цикл, в который можно встраиваться при помощи lifecycle hooks. Компоненты могут быть составлены в иерархическую структуру.
Синхронизация состояний и коммуникация между компонентом и шаблоном выполняется при помощи data binding. В процесс рендеринга шаблона в конечный HTML можно встроиться при помощи pipes (Angular) или value converters+binding behaviours (Aurelia).
Для перехода между изолированными областями приложения используется routing. Для коммуникации между модулями приложения могут быть использованы события.
И, наконец, переходим к примерам.
Начнем с создания простейшего компонента, который будет представлять собой root component.
import {Component} from 'angular2/core';
@Component({selector: 'angular-app', templateUrl: 'app/app.html'} })
export class App {
message: string = 'Welcome to Angular 2!';
}
Для того, чтобы Angular понял что наш модуль представляет из себя компонент, необходимо обернуть его декоратором @Component.
Примечание — строго говоря, @Component это не декоратор (decorator), а аннотация (annotation). Про разницу можно почитать здесь [36]. В статье оставим термин декоратор, поскольку в документации к Angular 2 аннотации лежат в разделе Decorators.
Как минимум, декоратор должен указывать селектор, куда отрисовывать шаблон. В данном случае это элемент <angular-app>.
Для декларации шаблона есть два варианта:
Шаблон будет выглядеть следующим образом:
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">{{message}}</a>
</div>
</div>
Вот, собственно, и весь компонент. Достаточно просто.
Что хорошо — код чище и свободнее от конструкций самого фреймворка, нежели это было в Angular 1. Это не может не радовать.
Что смущает — необходимость использования декораторов даже для простейшего компонента. Цитата из документации:
Each Angular component requires a single @Component and at least one View [37] annotation. The @Component annotation specifies when a component is instantiated, and which properties and hostListeners it binds to.
При этом параметры, конифгурируемые в данном примере, у большинства разработчиков в любом случае подчиняются какой-то логике, и вместо явной конфигурации можно было использовать конвенции, а декораторы подключать для более сложных сценариев.
Поскольку Aurelia построен основываясь на конвенциях, компонент это обычный модуль без каких-либо метаданных:
export class App {
message: string = "Welcome to Aurelia!";
}
По стандартной конвенции, для декларации шаблона нам необходимо создать html-файл с аналогичным компоненту именем, то есть для нашего примера это будет файл app.html:
<template>
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">${message}</a>
</div>
</div>
</template>
Каждый шаблон в Aurelia должен быть обернут в элемент template [38]. Для создания inline шаблона по аналогии с Angular 2 можно использовать декоратор inlineView [39]. Также в Aurelia можно изменять конвенции и выполнять дополнительную настройку. Детали можно посмотреть здесь [40].
Что хорошо — код предельно чистый и понятный. Никаких конструкций фреймворка.
Что смущает — для настройки может понадобиться множество аннотаций, решающих схожие вопросы. Например, это inlineView, noView, useView и useViewStrategy. Документацию пока что обильной не назовешь, и в ней даже нет поиска, так что есть риск просто запутаться, что и где использовать.
В нашем случае каждое приложение будет иметь несколько страничек, на которых будут рассматриваться аспекты, обозначенные в статье. Вложенный routing мы рассматривать не будем, поскольку как в Angular, так и в Aurelia, он по сути аналогичен routing-у как таковому.
Для того, чтобы настроить routing в Angular 2 нам необходимо импортировать декоратор @RouteConfig, импортировать модули, которые будут привязаны к маршрутам и декларировать нашу карту роутинга. Сделаем это в нашем компоненте app:
...
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {BindingSample} from './binding-sample/binding-sample';
import {ComponentSample} from './component-sample/component-sample';
...
@RouteConfig([
{ path: '/component-sample', name: 'ComponentSample', component: ComponentSample, useAsDefault: true },
{ path: '/binding-sample', name: 'BindingSample', component: BindingSample }
])
export class App {
...
}
Для каждого маршрута мы указываем:
Если смущает необходимость уже на старте загружать все модули, которые имеют маршрут, то можно воспользоваться асинхронной загрузкой. Для этого вместо параметра component используем параметр loader, указывая функцию, которая вернет promise, импортирующий нужный модуль. Мы так и сделаем, но пример кода чуть позже.
Чтобы использовать routing в шаблонах для отрисовки навигации и указания секции, куда отрисовывать текущий компонент, нам необходимо дополнительно импортировать коллекцию директив ROUTER_DIRECTIVES и добавить их в параметр directives декоратора @Component. Итого, модуль app.ts получается вот такой:
import {Component} from 'angular2/core';
import {View} from 'angular2/core';
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
@Component({
selector: 'angular-app', templateUrl: 'app/app.html', directives: [ROUTER_DIRECTIVES]
})
@RouteConfig([
{ path: '/component-sample', name: 'ComponentSample',
loader : () => System.import('app/component-sample/component-sample').then(m => m.ComponentSample), useAsDefault: true },
{ path: '/binding-sample', name: 'BindingSample',
loader : () => System.import('app/binding-sample/binding-sample').then(m => m.BindingSample) }
])
export class App {
message: string = "Welcome to Angular 2!";
}
Теперь добавляем навигацию в app.html. Для этого используем директиву routerLink [41], передавая в качестве параметра массив, первым элементом в котором является строка с именем маршрута, которое мы указали при настройке @RouteConfig.
…
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active">
<a [routerLink]="['ComponentSample']">Component sample</a>
</li>
<li>
<a [routerLink]="['BindingSample']">Binding sample</a>
</li>
</ul>
</div>
...
Параметр является массивом на случай вложенного routing, тогда мы передаем массив из нескольких строк с именами маршрутов.
Если маршрут необходимо параметризовать, то объект с параметрами передается последним элементом в массиве:
...
<a [routerLink]="['BindingSample', {someParameter: ‘someString’}]">Binding sample</a>
...
И последний момент. Нам необходимо в разметке указать область, куда будут отрисовываться шаблоны для текущего маршрута. Делаем это при помощи директивы router-outlet в том же app.html:
<div class="container">
<router-outlet></router-outlet>
</div>
Что хорошо — новый routing приятнее, чем routeProvider, живший в Angular 1.x, но все равно вызывает целый ряд вопросов. Абсолютное большинство типов, касающихся routing в документации вообще еще не имеют описания, поэтому пока трудно что-то говорить окончательно.
Что смущает — смущает несколько вещей. Прежде всего, это необходимость настройки через декоратор — если в нашем приложении, к примеру, 50 маршрутов, то код нашего модуля просто потеряется во всех этих настройках. И если нам понадобится при построении схемы навигации какая-либо if-логика, то наш код рискует превратиться в кошмар.
Второе, это отсутствие явного доступа ко всей коллекции route-ов, которую можно было бы просто перебрать в разметке для отрисовки всей навигации, а не ручная отрисовка каждой ссылки (которую мы будем забывать делать). Опять же, при наличии if-логики для построения маршрутов, нам придется дублировать эту логику в шаблоне, чтобы не нарисовать лишнего.
Согласно конвенции в Aurelia, чтобы настроить routing для компонента нам необходимо реализовать метод configureRouter, который Aurelia вызовет автоматически. То же самое верно и для вложенного routing-а — любой компонент, имеющий метод configureRouter, будет формировать схему routing-а.
export class App {
message: string = "Welcome to Aurelia!";
router: any;
configureRouter(config, router) {
config.title = 'Welcome to Aurelia!';
config.map([
{
route: ['', 'component-sample'], moduleId: 'app/component-sample/component-sample', nav: true, title: 'Component sample'
},
{
route: 'component-sample', moduleId: 'app/binding-sample/binding-sample', nav: true, title: 'Binding sample'
}
]);
this.router = router;
}
}
Для каждого маршрута мы указываем:
На основании переданной конфигурации Aurelia составит navigation model, который в шаблоне можно будет перебрать и отрисовать навигацию:
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li repeat.for="row of router.navigation" class="${row.isActive ? 'active' : ''}">
<a href.bind="row.href">${row.title}</a>
</li>
</ul>
</div>
Последним шагом указываем в разметке область, куда будут отрисовываться шаблоны для текущего маршрута. Делаем это при помощи директивы router-view в том же app.html
<div class="container">
<router-view></router-view>
</div>
Что хорошо — настройка простая и вполне очевидная. Приятно, что Aurelia пытается нам помочь и строит коллекцию для навигации.
Что смущает — по сравнению с навигацией в Durandal убрали возможность добавлять в настройки маршрутов произвольные свойства. С одной стороны это, конечно, правильно, ибо нефиг. С другой стороны, это сильно снижает вероятность использования navigation model в сторону ручной отрисовки. Navigation model будет бесполезен если захочется добавить к пунктам меню не только title, но и, например, tooltip-ы.
Для примера работы с вложенными компонентами в тестовом проекте есть папка component-sample, в ней лежит все, что нам нужно.
Итак, для создания вложенного компонента в Angular 2 нам необходимо:
import {Component} from 'angular2/core';
@Component({
selector: 'test-child-component',
inputs: ['inputMessage'],
template: `<div class="panel panel-default">
<div class="panel-heading">Child component title</div>
<div class="panel-body">
Message from parent component is: {{inputMessage}}
</div>
</div>`
})
export class TestChildComponent {
inputMessage: string
}
В итоге, наш родительский компонент выглядит следующим образом:
import {Component} from 'angular2/core';
import {TestChildComponent} from './test-сhild-сomponent';
@Component({
template: `
<div class="sample-header">
<h1>{{message}}</h1>
</div>
<test-child-component [inputMessage]="messageForChild"></test-child-component>`,
directives: [TestChildComponent]
})
export class ComponentSample
{
message: string = 'This is a component with child component sample';
messageForChild: string = 'Hello to child component!';
}
Что хорошо — в целом, все просто и понятно.
Что смущает — не хватает приятных плюшек, которые есть в Aurelia. Они описаны ниже.
В Aurelia отрисовать вложенный компонент можно двумя способами: custom element и composition.
Первый способ, в целом, аналогичен Angular 2 и используется для создания сложных контролов и т.д. В коде тестового проекта этот подход демонстрируется в файле test-custom-element.html.
Второй используется преимущественно для сценариев master-detail и более гибок, поскольку мы можем динамически указывать какой загрузить компонент, какой отрисовать шаблон и какие данные передать. Данный подход продемонстрирован в тестовом проект в файле test-сhild-сomponent.ts.
Разберем оба варианта по очереди.
Для создания вложенного компонента при помощи custom element нам необходимо:
<template bindable="message">
<div class="panel panel-default">
<div class="panel-heading">Custom element title</div>
<div class="panel-body">
Message from parent component is: ${message}
</div>
</div>
</template>
<template>
...
<require from="app/component-sample/test-custom-element.html"></require>
<test-custom-element message.bind="messageForCustomElement"></test-custom-element>
</template>
Если элемент используется повсеместно, то его можно зарегистрировать как global resource. Это позволит не писать в каждом шаблоне <require…>. Как это сделать написано здесь [40]в разделе “Making Resources Global”
Еще одна приятная опция, которая есть в Aurelia и которую мне не удалось найти в Angular — передача разметки из родительского шаблона. Если в родительском шаблоне внутри элемента test-custom-element декларировать разметку, а в дочернем добавить элемент
<content></content>
то разметка из родительского шаблона будет отрисована в дочернем. Также при работе с custom elements можно использовать уже упомянутые ранее template parts [28] и объявить несколько заменяемых областей.
Что хорошо — все просто и логично.
Что смущает — возможно, не всем понравится необходимость декларировать зависимости в разметке.
Для создания вложенного компонента при помощи composition нам необходимо:
import {inlineView} from 'aurelia-templating';
@inlineView('<template><h3>${inputMessage}</h3><template>')
export class TestChildComponent {
inputMessage: string;
activate(inputMessage: string)
{
this.inputMessage = inputMessage;
}
}
<compose model.bind="messageForChild" view-model="app/testChildComponent"></compose>
Что хорошо — на мой взгляд compose это очень крутой способ борьбы со сложностью проекта. Он обеспечивает изоляцию между компонентами при этом давая большую гибкость.
Что смущает — я долго размышлял над этим пунктом, но так и не смог ничего написать. Мне не к чему придраться.
Основная работа, которую команда Angular провела при разработке нового data binding, это минимизация количества используемых директив и четкое разделение направлений движения данных: to the DOM, from the DOM, both directions. Посмотрим на все по очереди.
Однонаправленный binding to the DOM выполняется при помощи двух форм:
string interpolation:
<div class="panel-body">
This string is builted with {{interpolationString}} syntax
</div>
и property binding:
<img [src]="iconUrl" />
В случае string interpolation текст, заключенный в двойные угловые скобки, оценивается как expression, который будет выполнен и его результат будет помещен в шаблон. То есть можно привязывать не только свойства компонента, но и писать выражения подобного вида:
<h1>2 = {{1+1}}</h1>
Однако, рекомендуется не злоупотреблять данной возможностью и сложные выражения помещать в свойства компонент.
В случае property binding ключевыми являются квадратные скобки, которые сигнализируют template engine, что это название свойства, которому необходимо присвоить результат выражения справа. В данной форме также поддерживаются выражения, но злоупотреблять ими не рекомендуется. Помимо стандартных свойств, есть дополнительный набор директив — routerLink, textContent и т.д.
Примечание — разрабатывая шаблоны в Angular 2, вы пишете, по сути, не на HTML, а на специальном синтаксисе Angular 2. Для разбора шаблона он использует собственный парсер. И, когда вы пишете, к примеру:
<input value="some text" />
то это не «чистый» HTML, в котором атрибуту присваивается значение, а синтаксический сахар, раскладывающийся в присвоение константы:
<input [value]="'some text'" />
Важным моментом является то, что для привязки данных к атрибутам, именам классов и стилям требуется отдельный синтаксис с использованием префиксов class, style, и attr. Например, вот так:
<img [style.width]="iconWidth" [style.height]="iconHeight" [src]="iconUrl" />
Для установки одновременно нескольких свойств стилей или нескольких классов имеются директивы ngClass и ngStyle. Как результат выражения они ожидают объект, каждое свойство которого будет оценено как свойство стиля или имя класса.
Смысл отделения property binding от attribute binding заключается в том, что html атрибуты и свойства DOM-элементов имеют четкое различие. Атрибуты используются для инициализации элементов, в то время как свойства предназначены для изменения состояния элементов и именно их использует Angular 2. Attribute binding является исключением на тот случай, если html атрибут не имеет соответствующего свойства в DOM-element.
В данном случае все просто, это привязка обработчиков событий:
<input type="button" (click)="onClicked()" value="Click me!" />
Ключевыми в данном варианте являются круглые скобки, внутри которых пишем название события (без префикса “on”). Внутри выражения в правой части binding-а доступна переменная $event, представляющая собой, собственно, DOM event. Это позволяет не декларировать отдельные функции для простейших обработчиков и писать inline выражения. Однако, опять же, увлекаться и писать таким образом тяжеловесные конструкции не стоит.
Двусторонняя привязка данных, по сути, может быть выполнена при помощи комбинации from the DOM и to the DOM:
<input [value]="twoWayBindedProperty" (input)="twoWayBindedProperty=$event.target.value" />
Однако, поскольку это абсолютно типичный сценарий, команда Angular разработала специальную директиву ngModel, которая вполне логично, хоть и немного жутко, объединяет синтаксис from the DOM и to the DOM в одну конструкцию:
<input [(ngModel)]="twoWayBindedProperty" />
При необходимости, можно разложить все это дело на отдельные части, тогда для направления from the DOM нам понадобится директива ngModelChange:
<input [ngModel]="twoWayBindedProperty" (ngModelChange)="twoWayBindedProperty=$event">
Примечание: если вас смущает синтаксис с [], () b [()] и вам больше нравится канонический вариант, то можно использовать приставки bind-,on- и bindon- соответственно. Например:
<img bind-src="iconUrl">
Что хорошо — несмотря на то, что синтаксис, мягко говоря, необычный и уже много было негативных высказываний в его адрес, лично мне он нравится. Во-первых, он очень заметный. Во-вторых, его очень легко запомнить. В-третьих, он хорошо подружится с intellisense различных редакторов, поскольку с первого же символа понятно, что это конструкция Angular 2 и мы сразу получим варианты директив и не придется запоминать все эти ngClass, ngModel и т.д. В-четвертых, его можно поменять на каноническую форму, если вам совсем уж противно от всех этих скобочек.
Что смущает — в противовес Aurelia не хватает возможности one-time привязки и event delegation. Но критичными эти вещи, в общем случае, не назовешь.
В Aurelia data binding сосредоточен на возможности тонкой настройки направления движения данных. Фреймворк делает некие разумные предположения, но оставляет разработчику возможность решать самому. В конечном счете у нас есть следующие варианты:
<div class="panel-body">
This string is builted with ${interpolationString} syntax
</div>
Аналогичен string interpolation в Angular 2, отличается только синтаксис (${} вместо {{}}):
Cводится к выражениям <имя свойства>.<тип привязки> = “выражение”. Типы привязки есть следующие:
Наглядно, синтаксис для property binding, в общем виде, следующий:
<input value.bind="iconUrl" />
Помимо привязки стандартных свойств, как и в Angular 2, имеются custom атрибуты innerHTML, textContent, style. Возможны два варианта написания — с добавлением .bind и указанием свойства, или без .bind и с использованием string interpolation:
<div innerhtml.bind="htmlProperty"></div>
<div innerhtml="${htmlProperty}"></div>
Для привязки обработчиков событий у нас есть два варианта:
Для доступа к событию внутри выражения обработчика, как и в Angular 2, доступна переменная $event.
Примечание: в своем выступлении на NDC London [33] Эйзенберг говорит о том, что синтаксис Aurelia можно легко переписать при помощи плагинов и демонстрирует data binding в Aurelia с использованием синтаксиса Angular 2. Однако, найти этот плагин где-либо мне не удалось.
Что хорошо — возможность тонкой настройки, если она вам нужна. Вариант привязки one time. Полная совместимость со стандартами HTML.
Что смущает — достаточно многословный синтаксис для вариантов one-time, one-way и two-way. И поддержка intellisense будет не такой крутой, как в Angular.
Вдаваться в детали не будем, поскольку, полагаю, читатель уже начал уставать. Просто посмотрим общий синтаксис:
<div class="panel-body">
Select something:
<select [(ngModel)]="selectedClass">
<option *ngFor="#alertClass of alertClasses" [value]="alertClass">{{alertClass}}</option>
</select>
</div>
<div class="panel-body">
<div [ngSwitch]="selectedClass">
<template [ngSwitchWhen]="'success'">
<div class="alert alert-success" role="alert">You will be successfull if you learn Angular</div>
</template>
...
<template ngSwitchDefault>You must choose option</template>
</div>
<div *ngIf="selectedClass=='success'">
<div class="alert alert-success" role="alert">Extra message with *ngIf binding</div>
</div>
</div>
В целом ничего необычного. На что стоит обратить внимание, это синтаксис ngFor и ngIf — директивы предваряет символ *. Как объясняется в документации, это своего рода синтаксический сахар, который позволяет избежать оборачивания шаблонов в элемент template.
Локальные переменные необходимы для доступа к данным из разных областей html-шаблона. Простейший пример создания переменной мы уже увидели в цикле ngFor:
<option *ngFor="#alertClass of alertClasses" [value]="alertClass">{{alertClass}}</option>
С помощью спецсимвола # мы указали Angular, что объявляем переменную, к которой будем обращаться внутри шаблона. Также можно создать переменную, указывающую индекс текущего элемента массива:
<option *ngFor="#alertClass of alertClasses, #index=index" [value]="alertClass">{{index}} {{alertClass}}</option>
Еще объявление переменных при помощи # (или канонической альтернативы “val-”) можно применять вне цикла ngFor. В таком случае, переменная будет указывать на html элемент в котором переменная была объявлена. Эту переменную можно использовать, например, в обработчиках событий. Но в шаблоне такие переменные не работают, то есть watch не выполняется:
<input #i placeholder="Type something">
<input type="button" class="btn btn-success" (click)="displayTextboxValue(i.value)" value="And click" />
<br/>
But templating doesn't work with it - {{i.value}}
Начать стоит с того, что в Aurelia switch не реализован (по крайней мере пока). Также, стоит отметить, что в терминологии Aurelia управляющие конструкции относятся не к data binding, а к HTML extensions, как и виденный нами ранее compose [27].
Итого, из управляющих конструкций у нас есть repeat, if и притянутый сюда же за компанию show:
<div class="panel-body">
Select something:
<select value.bind="selectedClass">
<option repeat.for="alertClass of alertClasses" value.bind="alertClass">${alertClass}</option>
</select>
</div>
<div class="panel-body">
<div if.bind="selectedClass=='success'" class="alert alert-success" role="alert">You will be successfull if you learn Aurelia</div>
...
<div show.bind="selectedClass=='success'">
<div class="alert alert-success" role="alert">Extra message with show extension</div>
</div>
</div>
Дополнительно, отмечу, что при написании выражений внутри repeat доступны следующие переменные:
Синтаксис для создания локальных переменных внутри repeat мы уже увидели выше. Для сравнения с Angular 2 осталось посмотреть, как создавать переменные, указывающие на HTML элементы. Для этого используется атрибут ref. Созданную переменную можно использовать как в обработчиках событий, так и в шаблоне, то есть watch выполняется:
<div class="panel-body">
<input ref="i" placeholder="Type something">
<input type="button" class="btn btn-success" click.delegate="displayTextboxValue(i.value)" value="And click" />
<br/> And templating works with it - ${i.value}
</div>
Что смущает — внимательный читатель мог заметить, что в части Angular 2 не было заметок «Что хорошо — Что смущает». Я решил объединить этот раздел и написать по поводу обоих, ибо проблема у них одна. Эта проблема заключается в том, что при ошибках в шаблонах, далеко не всегда оба бросают ошибки. Например, попробуйте написать неправильно имя свойства в примере с if для обоих фреймворков — оба продолжат втихушку работать.
А значит мы остаемся наедине со своими ошибками и опечатками.
На этом все. Спасибо дочитавшим :)
Автор: fshchudlo
Источник [43]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/111443
Ссылки в тексте:
[1] Angular 2: http://angularjs.blogspot.ru/2015/12/angular-2-beta.html
[2] Aurelia: http://blog.durandal.io/2015/11/16/aurelia-beta-week-day-1-the-beta-is-here/
[3] github: https://github.com/fshchudlo/angular-aurelia
[4] systemjs: https://github.com/systemjs/systemjs
[5] http-server: https://github.com/indexzero/http-server
[6] есть: https://github.com/indexzero/http-server/pull/194
[7] совсем немного: http://habrahabr.ru/search/?q=aurelia
[8] Роба Эйзенберга: https://twitter.com/EisenbergEffect
[9] Caliburn.Micro: http://caliburnmicro.com/
[10] Durandal: http://durandaljs.com/
[11] так называемого NextGen фреймворка: http://eisenbergeffect.bluespire.com/preparing-for-durandal-nextgen/
[12] John Papa: http://johnpapa.net/
[13] Брэдом Грином: https://github.com/bradlygreen
[14] начал работать в команде Angular: http://eisenbergeffect.bluespire.com/angular-and-durandal-converge/
[15] принял решение покинуть команду Angular: http://eisenbergeffect.bluespire.com/leaving-angular/
[16] Scott Allen: https://twitter.com/OdeToCode
[17] стал Aurelia: http://eisenbergeffect.bluespire.com/introducing-aurelia/
[18] 5000 звезд: https://github.com/aurelia/framework
[19] 8000 у Angular 2: https://github.com/angular/angular
[20] Aurelia: http://jdanyow.github.io/aurelia-dbmonster/
[21] Angular 2: http://mathieuancelin.github.io/js-repaint-perfs/angular2/opt.html
[22] React: http://mathieuancelin.github.io/js-repaint-perfs/react/opt.html
[23] Angular1: http://mathieuancelin.github.io/js-repaint-perfs/angular/opt.html
[24] Ryan Florence: https://twitter.com/ryanflorence
[25] представил реализацию для Aurelia: http://www.danyow.net/aurelia-dbmonster/
[26] Batarangle: http://go.rangle.io/batarangle
[27] composition: https://github.com/aurelia/documentation/blob/master/English/docs.md#compose
[28] template parts: https://github.com/aurelia/documentation/blob/master/English/docs.md#template-parts
[29] подобные посты: http://www.towfeek.se/2015/07/angularjs-2-vs-aurelia/
[30] раз: http://blog.durandal.io/2015/03/16/aurelia-and-angular-2-code-side-by-side/
[31] два: http://blog.durandal.io/2015/03/17/aurelia-angular-2-0-code-side-by-side-part-2/
[32] три: http://blog.durandal.io/2015/05/20/porting-an-angular-2-0-app-to-aurelia/
[33] NDC London: https://vimeo.com/153090562?utm_content=buffer79560&utm_medium=social&utm_source=twitter.com&utm_campaign=buffer
[34] Angular 2: http://developer.telerik.com/featured/will-angular-2-be-a-success-you-bet/#comment-2346150095
[35] закрытии parse.com: https://habrahabr.ru/post/276135/
[36] здесь: http://blog.thoughtram.io/angular/2015/05/03/the-difference-between-annotations-and-decorators.html
[37] View: https://habrahabr.ru/users/view/
[38] template: https://developer.mozilla.org/ru/docs/Web/HTML/Element/template
[39] inlineView: http://aurelia.io/docs.html#/aurelia/templating/latest/doc/api/overview
[40] здесь: http://aurelia.io/docs.html#/aurelia/framework/1.0.0-beta.1.0.8/doc/article/app-configuration-and-startup
[41] routerLink: https://angular.io/docs/ts/latest/api/router/RouterLink-directive.html
[42] lifecycle hooks: https://github.com/aurelia/documentation/blob/master/English/docs.md#the-screen-activation-lifecycle
[43] Источник: https://habrahabr.ru/post/276649/
Нажмите здесь для печати.