Aurelia против Angular 2 — сравнение кода
Опубликовано: 2022-03-11Angular и Aurelia, потомки старого доброго JavaScript Angular 1, являются жесткими конкурентами, разработанными и выпущенными примерно в одно и то же время и с похожей философией, но отличающимися рядом ключевых моментов. В этой статье мы проведем параллельное сравнение этих различий в функциях и коде.
Короче говоря, Аурелия была создана Робом Айзенбергом, известным как создатель Дюрандаля и Калибурна. Он работал в команде Angular 2 в Google, но ушел в 2014 году, когда его взгляды на то, как должен выглядеть современный фреймворк, отличались от их взглядов.
Сходство сохраняется и на более техническом уровне: шаблоны и связанные с ними компоненты (или пользовательские элементы) являются основными как для приложений Angular, так и для приложений Aurelia, и оба требуют наличия корневого компонента (т. е. приложения). Кроме того, и Angular, и Aurelia активно используют декораторы для настройки компонентов. У каждого компонента есть фиксированный жизненный цикл, к которому мы можем подключиться.
Так в чем же разница между Aurelia и Angular 2?
Ключевое отличие, по словам Роба Айзенберга, в коде: Аурелия ненавязчива. При разработке приложения Aurelia (после настройки) вы пишете на ES6 или TypeScript, и шаблоны выглядят как абсолютно вменяемый HTML, особенно по сравнению с Angular. Aurelia — это соглашение, а не конфигурация, и в 95% случаев вы будете в порядке, используя соглашения по умолчанию (такие как имена шаблонов, имена элементов и т. д.), в то время как Angular требует, чтобы вы предоставили конфигурацию практически для всего.
Aurelia также считается более совместимой со стандартами, хотя бы потому, что она не чувствительна к регистру, когда речь идет о HTML-тегах, в отличие от Angular 2. Это означает, что Angular 2 не может полагаться на парсер HTML браузера, поэтому они создали свой собственный.
Еще один фактор, который следует учитывать при выборе между SPA-фреймворками, — это сообщество — экосистема — вокруг них. И в Angular, и в Aurelia есть все основы (маршрутизатор, механизм шаблонов, проверка и т. д.), и легко получить собственный модальный режим или использовать какую-либо стороннюю библиотеку, но неудивительно, что Angular имеет большее сообщество и большую разработку. команда.
Кроме того, хотя обе платформы имеют открытый исходный код, Angular в основном разработан Google и не предназначен для коммерциализации, в то время как Durandal, Inc., в которой работает основная команда, следует модели монетизации Ember.js посредством консультаций и обучения.
Aurelia против Angular: сравнение кода
Давайте рассмотрим некоторые из наиболее примечательных особенностей, которые подчеркивают философию каждого фреймворка.
После клонирования исходных проектов для Angular и Aurelia у нас есть приложение ES6 Aurelia (вы можете использовать Jspm/System.js, Webpack и RequireJS в сочетании с ES6 или TypeScript) и приложение TypeScript Angular (WebPack) соответственно.
Давайте катиться.
Привязка данных
Прежде чем сравнивать параллельные рабочие примеры, мы должны взглянуть на некоторые синтаксические различия между Aurelia и Angular 2, а именно в ключевой функциональности привязки значений из контроллера к представлению. Angular 1 использовал «грязную проверку» для всего, метод, который сканировал область изменений. Понятно, что это вызвало ряд проблем с производительностью. Ни Angular 2, ни Aurelia не пошли по этому пути. Вместо этого они используют привязку событий.
Привязка данных в Angular 2
В Angular вы связываете данные с помощью квадратных скобок и используете круглые скобки для привязки событий, например:
<element [property]="value"></a> <element (someEvent)="eventHandler($event)"></a>
Двусторонняя привязка — когда вы хотите, чтобы изменения в данных приложения отражались в представлении и наоборот — представляет собой комбинацию квадратных скобок и скобок. Таким образом, для двустороннего ввода это будет работать нормально:
<input type="text" [(ngModel)]="text"> {{text}}
Другими словами, круглые скобки представляют собой событие, а квадратные скобки представляют собой значение, передаваемое на вход.
Команда Angular отлично поработала, разделив направления привязки: к DOM, от DOM и двунаправленное. Там также много синтаксического сахара, связанного с классами и стилями привязки. Рассмотрим, например, следующий фрагмент в качестве примера односторонней привязки:
<div [class.red-container]="isRed"></div> <div [style.width.px]="elementWidth"></div>
Но что, если мы хотим привязать двусторонние данные к компоненту? Рассмотрим следующую базовую настройку ввода:
<!-- parent component --> <input type="text" [(ngModel)]="text"> {{ text }} <my-component [(text)]="text"></my-component> import {Component, Input} from '@angular/core'; @Component(/* ... */) export class MyComponent { @Input() text : string; } <!-- child component --> <input [(ngModel)]="text"> Text in child: {{ text }}
Обратите внимание, что для использования ngModel
ваш модуль должен импортировать FormsModule из @angular/forms
. Теперь у нас есть кое-что интересное. Обновление значения родительского ввода изменяет значения везде, но изменение дочернего ввода влияет только на этот дочерний элемент. Если мы хотим, чтобы он обновил родительское значение, нам нужно событие, информирующее родителя. Соглашение об именах для этого события: property name + 'Change'
, например:
import {Component, Input, Output, EventEmitter} from '@angular/core'; @Component(/* ... */) export class MyComponent { @Input() text : string; @Output() textChange = new EventEmitter(); triggerUpdate() { this.textChange.emit(this.text); } }
Двусторонняя привязка начинает работать правильно сразу после привязки к событию ngModelChange
:
<!-- child component --> <input [(ngModel)]="text" (ngModelChange)="triggerUpdate($event)">
Как насчет однократной привязки, когда вы эффективно указываете фреймворку игнорировать связанные значения, даже если они изменяются?
В Angular 1 мы использовали {{::value}} для привязки один раз. В Angular 2 одноразовая привязка усложняется: в документации сказано, что вы можете использовать changeDetection: ChangeDetectionStrategy.OnPush
в конфигурации Компонента, но это сделает все ваши привязки одноразовыми.
Привязка данных: путь Аурелии
В отличие от Angular 2, привязка данных и событий в Aurelia очень проста. Вы можете использовать либо интерполяцию, как в Angular property="${value}"
, либо использовать один из следующих типов привязки:
property.one-time="value" property.one-way="value" property.two-way="value"
Названия говорят сами за себя. Кроме того, есть property.bind="value"
— синтаксический сахар, который самостоятельно определяет, должна ли привязка быть односторонней или двусторонней. Рассмотреть возможность:
<!-- parent--> <template bindable="text"> <input type="text" value.bind="text"/> <child text.two-way="text"></child> </template> <!-- child custom element --> <template bindable="text"> <input type="text" value.bind="text"/> </template>
В приведенном выше фрагменте и @bindable
, и @Input
можно настраивать, поэтому вы можете легко изменить такие вещи, как имя связываемого свойства и т. д.
А события? Для привязки к событиям в Aurelia вы используете .trigger
и .delegate
. Например, чтобы дочерний компонент запускал событие, вы можете сделать следующее:
// child.js this.element.dispatchEvent(new CustomEvent('change', { detail: someDetails }));
Затем, чтобы прослушать это в родительском:
<child change.trigger="myChangeHandler($event)"></child> <!-- or --> <child change.delegate="myChangeHandler($event)"></child>
Разница между ними в том, что .trigger
создает обработчик событий для этого конкретного элемента, а .delegate
добавляет прослушиватель для document
. Это экономит ресурсы, но, очевидно, не будет работать для событий без всплытия.
Базовый пример Aurelia против Angular
Теперь, когда мы рассмотрели привязку, давайте создадим базовый компонент, который отображает масштабируемую векторную графику (SVG). Это будет круто, поэтому мы назовем его awesome-svg
. Это упражнение проиллюстрирует как базовые функции, так и философию Aurelia и Angular 2. Примеры кода Aurelia из этой статьи доступны на GitHub.
Пример прямоугольников SVG в Aurelia
Давайте сначала создадим файл JavaScript:
// awesome-svg.js import {bindable} from 'aurelia-framework'; export class AwesomeSvgCustomElement { @bindable title; @bindable colors = []; }
Теперь о HTML.
В Aurelia можно указать шаблон (или использовать встроенный) с аннотациями @template
, @inlineView
или даже @noView
, но по умолчанию он ищет файл .html
с тем же именем, что и .js
файл. То же самое верно и для имени пользовательского элемента — вы можете установить его с помощью @customElement('awesome-svg')
, но если вы этого не сделаете, Aurelia преобразует заголовок в тире и будет искать совпадение.
Поскольку мы не указали иначе, элемент будет называться awesome-svg
, и Aurelia будет искать шаблон с тем же именем, что и файл js
(например awesome-svg.html
) в том же каталоге:
<!-- awesome-svg.html --> <template> <h1>${title}</h1> <svg> <rect repeat.for="color of colors" fill.bind="color" x.bind="$index * 100" y="0" width="50" height="50"></rect> </svg> </template>
Обратите внимание на <template>
? Все шаблоны должны быть заключены в <template>
. Также стоит отметить, что вы используете `for… of and the string interpolation
${title}` так же, как и в ES6.
Теперь, чтобы использовать компонент, мы должны либо импортировать его в шаблон с помощью <require from="path/to/awesome-svg"></require>
, либо, если он используется во всем приложении, глобализировать ресурс в функции конфигурации фреймворка. с aurelia.use.globalResources('path/to/awesome-svg');
, который раз и навсегда импортирует компонент awesome-svg
.
[Обратите внимание: если вы не сделаете ни того, ни другого, <awesome-svg></awesome-svg>
будет обрабатываться так же, как и любой другой HTML-тег, без ошибок.]
Вы можете отобразить компонент с помощью:
<awesome-svg colors.bind="['#ff0000', '#00ff00', '#0000ff']"></awesome-svg>
Это отображает набор из 3 прямоугольников:
Пример прямоугольников SVG в Angular 2
Теперь давайте сделаем тот же пример в Angular 2, также доступном на GitHub. Angular 2 требует, чтобы мы указывали как шаблон, так и имя элемента:
// awesome-svg.component.ts import {Component, Input} from '@angular/core'; @Component({ selector: 'awesome-svg', templateUrl: './awesome-svg.component.html' }) export class AwesomeSvgComponent { @Input() title : string; @Input() colors : string[] = [] }
С видом все становится немного сложнее. Прежде всего, Angular молча обрабатывает неизвестные теги HTML так же, как это делает браузер: он выдает ошибку о том, что что-то вроде строк my-own-tag
является неизвестным элементом. Он делает то же самое для любых свойств, которые вы связываете, поэтому, если у вас где-то в коде есть опечатка, это привлечет большое внимание, потому что приложение вылетит. Звучит хорошо, правда? Да, потому что вы бы сразу заметили, если бы сломали приложение, а нет, потому что это просто дурной тон.
Рассмотрим этот фрагмент, который отлично подходит с точки зрения синтаксиса привязки:
<svg> <rect [fill]="color"></rect> </svg>
Несмотря на то, что он читается нормально, вы получите сообщение об ошибке, например, «невозможно выполнить привязку к 'fill', поскольку это не известное свойство ':svg:rect'». Чтобы исправить это, вам нужно вместо этого использовать синтаксис [attr.fill]="color"
. Также обратите внимание, что необходимо указать пространство имен в дочерних элементах внутри <svg/>: <svg:rect>
, чтобы Angular знал, что это не следует рассматривать как HTML. Давайте расширим наш фрагмент:
<!-- awesome-svg.component.html--> <h1>{{ title }}</h1> <svg> <rect *ngFor="let color of colors; let i = index" [attr.fill]="color" [attr.x]="i * 100" y="0" width="50" height="50" ></rect> </svg>
Ну вот. Далее импортируем его в конфигурацию модуля:

@NgModule({ declarations: [ AwesomeSvgComponent ] //... })
Теперь компонент можно использовать в этом модуле, например:
<awesome-svg [colors]="['#ff0000', '#00ff00', '#0000ff']" title="Rectangles"></awesome-svg>
Пользовательские элементы
Предположим, теперь мы хотим, чтобы наш код прямоугольника был настраиваемым компонентом со своей собственной логикой.
Пользовательские элементы: Angular 2 Way
Поскольку Angular 2 отображает компоненты по тому, что соответствует его определенному селектору, очень легко определить пользовательский компонент, например:
@Component({ selector: 'g[custom-rect]', ... })
Вышеприведенный фрагмент отобразит пользовательский элемент для любых тегов <g custom-rect></div>
, что очень удобно.
Пользовательские элементы: путь Аурелии
Aurelia позволяет нам создавать пользовательские элементы только для шаблонов:
<template bindable="colors, title"> <h1>${title}</h1> <svg> <rect repeat.for="color of colors" fill.bind="color" x.bind="$index * 100" y="0" width="50" height="50"></rect> </svg> </template>
Пользовательский элемент будет назван в соответствии с именем файла. Единственное отличие от именования других компонентов заключается в том, что при импорте, либо в конфигурировании, либо через <require>
, вы должны поставить .html
в конце. Например: <require from="awesome-svg.html"></require>
.
В Aurelia тоже есть пользовательские атрибуты, но они не служат той же цели, что и в Angular 2. Например, в Aurelia вы можете использовать аннотацию @containerless
для пользовательского элемента rect
. @containerless
также можно использовать с пользовательскими шаблонами без контроллера и <compose>
, что в основном отображает материал в DOM.
Рассмотрим следующий код, содержащий аннотацию @containerless
:
<svg> <custom-rect containerless></custom-rect> </svg>
Вывод не будет содержать тег пользовательского элемента ( custom-rect
), но вместо этого мы получим:
<svg> <rect ...></rect> </svg>
Услуги
Что касается сервисов, Aurelia и Angular очень похожи, как вы увидите в следующих примерах. Предположим, нам нужен NumberOperator
, который зависит от NumberGenerator
.
Услуги в Аурелии
Вот как определить наши две службы в Aurelia:
import {inject} from 'aurelia-framework'; import {NumberGenerator} from './number-generator' export class NumberGenerator { getNumber(){ return 42; } } @inject(NumberGenerator) export class NumberOperator { constructor(numberGenerator){ this.numberGenerator = numberGenerator; this.counter = 0; } getNumber(){ return this.numberGenerator.getNumber() + this.counter++; } }
Теперь для компонента мы вводим таким же образом:
import {inject} from 'aurelia-framework'; import {NumberOperator} from './_services/number-operator'; @inject(NumberOperator) export class SomeCustomElement { constructor(numberOperator){ this.numberOperator = numberOperator; //this.numberOperator.getNumber(); } }
Как видите, с внедрением зависимостей любой класс может быть полностью расширяемой службой, так что вы даже можете написать свои собственные преобразователи.
Заводы в Аурелии
Если вам нужна фабрика или новый экземпляр ( Factory
и NewInstance
— это всего лишь пара популярных распознавателей, поставляемых из коробки), вы можете сделать следующее:
import { Factory, NewInstance } from 'aurelia-framework'; @inject(SomeService) export class Stuff { constructor(someService, config){ this.someService = someService; } } @inject(Factory.of(Stuff), NewInstance.of(AnotherService)) export class SomethingUsingStuff { constructor(stuffFactory, anotherService){ this.stuff = stuffFactory(config); this.anotherServiceNewInstance = anotherService; } }
Угловые сервисы
Вот тот же набор сервисов в Angular 2:
import { Injectable } from '@angular/core'; import { NumberGenerator } from './number-generator'; @Injectable() export class NumberGenerator { getNumber(){ return 42; } } @Injectable() export class NumberOperator { counter : number = 0; constructor(@Inject(NumberGenerator) private numberGenerator) { } getNumber(){ return this.numberGenerator.getNumber() + this.counter++; } }
Аннотация @Injectable
обязательна, и чтобы фактически внедрить сервис, вам нужно указать сервис в списке провайдеров в конфигурации компонента или всей конфигурации модуля, например:
@Component({ //... providers: [NumberOperator, NumberGenerator] })
Или, что не рекомендуется, вы также можете указать его в bootstrap(AppComponent, [NumberGenerator, NumberOperator])
.
Обратите внимание, что вам нужно указать как NumberOperator
, так и NumberGenerator
, независимо от того, как вы его вводите.
Полученный компонент будет выглядеть примерно так:
@Component({ //... providers: [NumberOperator, NumberGenerator], }) export class SomeComponent { constructor(@Inject(NumberOperator) public service){ //service.getNumber(); } }
Фабрики в Angular 2
В Angular 2 вы можете создавать фабрики с аннотацией provide
, которая также используется для сервисов псевдонимов, чтобы предотвратить конфликты имен. Создание фабрики может выглядеть следующим образом:
let stuffFactory = (someService: SomeService) => { return new Stuff(someService); } @Component({ //... providers: [provide(Stuff, {useFactory: stuffFactory, deps: [SomeService]})] })
трансклюзия
В Angular 1 была возможность включать контент, «слот», из одного шаблона в другой с помощью включения. Посмотрим, что могут предложить его потомки.
Контент-проекция с Angular 2
В Angular 2 трансклюзия называется «проекцией контента» и работает так же, как ng-transclude
; т. е., говоря в терминах Angular 1, включенный контент использует родительскую область. Он будет соответствовать включенному тегу контента на основе селектора конфигурации. Рассмотреть возможность:
@Component({ selector: 'child', template: `Transcluded: <ng-content></ng-content>` }) export class MyComponent {}
Затем вы можете использовать компонент с <child-component>Hello from Translusion Component</child-component>
, и мы получим точный включенный HTML-код Yes
, отображаемый в дочернем компоненте.
Для включения нескольких слотов в Angular 2 есть селекторы, которые вы можете использовать так же, как и для конфигурации @Component
:
<!-- child.component.html --> <h4>Slot 1:</h4> <ng-content select=".class-selector"></ng-content> <h4>Slot 2:</h4> <ng-content select="[attr-selector]"></ng-content>
<!-- parent.component.html --> <child> <span class="class-selector">Hello from Translusion Component</span> <p class="class-selector">Hello from Translusion Component again</p> <span attr-selector>Hello from Translusion Component one more time</span> </child>
Вы можете использовать select
для своих пользовательских тегов, но помните, что тег должен быть известен Angular 2.
Слоты с Аурелией
Помните, я сказал, что Aurelia всегда следует веб-стандартам? В Aurelia трансклюзия называется слотами, и это просто полифилл для теневого DOM веб-компонентов. Shadow DOM еще не создан для слотов, но соответствует спецификациям W3C.
<!-- child --> <template> Slot: <slot></slot> </template> <!-- parent --> <template> <child>${textValue}</child> </template>
Aurelia была разработана, чтобы соответствовать стандартам, а Angular 2 — нет. В результате мы можем делать более удивительные вещи со слотами Aurelia, например использовать резервный контент (попытка использовать резервный контент в Angular 2 терпит неудачу с <ng-content> element cannot have content
). Рассмотреть возможность:
<!-- child --> <template> Slot A: <slot name="slot-a"></slot> <br /> Slot B: <slot name="slot-b"></slot> Slot C: <slot name="slot-c">Fallback Content</slot> </template> <!-- parent --> <template> <child> <div slot="slot-a">A value</div> <div slot="slot-b">B value</div> </child> </template>
Так же, как и Angular 2, Aurelia будет отображать все вхождения слота на основе совпадения имени.
Также стоит отметить, что как в Aurelia, так и в Angular вы можете компилировать части шаблона и динамически отображать компоненты (используя <compose>
с view-model
в Aurelia или ComponentResolver
в Angular 2).
Тень ДОМ
И Aurelia, и Angular поддерживают Shadow DOM.
В Aurelia просто используйте декоратор @useShadowDOM
, и все готово:
import {useShadowDOM} from 'aurelia-framework'; @useShadowDOM() export class YetAnotherCustomElement {}
В Angular то же самое можно сделать с ViewEncapsulation.Native
:
import { Component, ViewEncapsulation } from '@angular/core'; @Component({ //... encapsulation: ViewEncapsulation.Native, }) export class YetAnotherComponent {}
Не забудьте проверить, поддерживает ли ваш браузер Shadow DOM.
Рендеринг на стороне сервера
Это 2017 год, и рендеринг на стороне сервера очень моден. Вы уже можете рендерить Angular 2 на серверной части с помощью Angular Universal, и Aurelia будет иметь это в 2017 году, как указано в новогодних решениях ее команды. На самом деле, в репозитории Аурелии есть работающая демоверсия.
В дополнение к этому, Aurelia уже более года имеет возможности прогрессивного улучшения, чего нет в Angular 2 из-за его нестандартного синтаксиса HTML.
Размер, производительность и будущее
Хотя это не показывает нам всей картины, тесты DBMonster с конфигурациями по умолчанию и оптимизированной реализацией рисуют хорошую сравнительную картину: Aurelia и Angular показывают примерно 100 повторных рендеров в секунду (при тестировании на MacBook Pro). в то время как Angular 1 показал примерно половину этого результата. И Aurelia, и Angular превосходят Angular 1 примерно в пять раз, и оба на 40% опережают React. Ни Aurelia, ни Angular 2 не имеют реализации Virtual DOM.
Что касается размера, Angular примерно в два раза толще Aurelia, но ребята из Google работают над этим: дорожная карта Angular включает в себя выпуск Angular 4 с планами сделать его меньше и легче, одновременно улучшая опыт разработчиков. Angular 3 не существует, и действительно, когда речь идет об Angular, номер версии следует опустить, так как основные выпуски планируются каждые 6 месяцев. Если вы посмотрите на путь, который прошел Angular от альфы до своей текущей версии, вы увидите, что он не всегда был связан с такими вещами, как переименование атрибутов из сборки в сборку и т. д. Команда Angular обещает, что сломать изменения будет легко. мигрировать.
С 2017 года команда Aurelia планирует выпустить Aurelia UX, предоставить больше интеграций и инструментов, а также внедрить рендеринг на стороне сервера (который уже давно находится в планах).
Angular 2 против Aurelia: дело вкуса
И Angular, и Aurelia хороши, и выбор одного из них зависит от личного вкуса и приоритетов. Если вам нужно более тонкое решение, Aurelia — ваш лучший вариант, но если вам нужна поддержка сообщества, Angular — ваш лучший выбор. Это не вопрос «Позволяет ли мне этот фреймворк…?» потому что ответ просто «да». Они обеспечивают примерно одинаковую функциональность, но придерживаются разных принципов и стилей, а также совершенно другого подхода к веб-стандартам.