Aurelia vs. Angular 2——代码比较

已发表: 2022-03-11

Angular 和 Aurelia 是优秀的旧 JavaScript Angular 1 的后代,它们是激烈的竞争者,它们几乎同时开发和发布,并具有相似的理念,但在许多关键方面有所不同。 在本文中,我们将对功能和代码方面的这些差异进行并排比较。

长话短说,Aurelia 是由被称为 Durandal 和 Caliburn 的创造者的 Rob Eisenberg 创建的。 他曾在 Google 的 Angular 2 团队工作,但在 2014 年离开时,他对现代框架应该如何看待的看法与他们的不同。

在技​​术层面上也存在相似之处:模板和与之关联的组件(或自定义元素)是 Angular 和 Aurelia 应用程序的核心,两者都要求您拥有一个根组件(即应用程序)。 此外,Angular 和 Aurelia 都大量使用装饰器进行组件配置。 每个组件都有一个我们可以挂钩的固定生命周期。

那么 Aurelia 和 Angular 2 有什么区别呢?

根据 Rob Eisenberg 的说法,关键区别在于代码:Aurelia 不引人注目。 在开发 Aurelia 应用程序时(在配置之后),您使用 ES6 或 TypeScript 编写,模板看起来绝对像 HTML,尤其是与 Angular 相比。 Aurelia 是约定优于配置,在 95% 的情况下,使用默认约定(例如模板命名、元素命名等)就可以了,而 Angular 则要求您为基本上所有内容提供配置。

Aurelia 也被认为更符合标准,只是因为它在 HTML 标签方面不区分大小写,而 Angular 2 是。 这意味着 Angular 2 不能依赖浏览器的 HTML 解析器,所以他们创建了自己的。

在 SPA 框架之间进行选择时要考虑的另一个因素是它们周围的社区——生态系统。 Angular 和 Aurelia 都具备所有基础知识(路由器、模板引擎、验证等),并且很容易获得原生模式或使用一些第三方库,但 Angular 拥有更大的社区和更大的开发也就不足为奇了团队。

此外,虽然这两个框架都是开源的,但 Angular 主要由谷歌开发,不打算商业化,而雇佣核心团队的 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 ,您的模块必须从@angular/forms导入 FormsModule。 现在我们有了一些有趣的东西。 更新父输入中的值会更改各处的值,但修改子输入只会影响该子。 如果我们希望它更新父值,我们需要一个事件通知父值。 此事件的命名约定是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属性,但这将使您的所有绑定一次性完成。

数据绑定:Aurelia 方式

与 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在该特定元素上创建一个事件处理程序,而.delegatedocument上添加一个侦听器。 这可以节省资源,但显然不适用于非冒泡事件。

Aurelia 与 Angular 的基本示例

现在我们已经介绍了绑定,让我们创建一个渲染可缩放矢量图形 (SVG) 的基本组件。 它会很棒,因此我们称它为awesome-svg 。 本练习将说明 Aurelia 和 Angular 2 的基本功能和理念。本文的 Aurelia 代码示例可在 GitHub 上获得。

Aurelia 中的 SVG 矩形示例

让我们首先构建 JavaScript 文件:

 // awesome-svg.js import {bindable} from 'aurelia-framework'; export class AwesomeSvgCustomElement { @bindable title; @bindable colors = []; }

现在是 HTML。

在 Aurelia 中,您可以使用注释@template@inlineView甚至@noView指定模板(或使用内联模板),但开箱即用,它会搜索与.js同名的.html文件文件。 自定义元素的名称也是如此——您可以使用@customElement('awesome-svg')设置它,但如果不这样做,Aurelia 会将标题转换为 dash-case 并查找匹配项。

由于我们没有另外指定,该元素将被称为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 个矩形:

Aurelia 中的 SVG 矩形示例

Angular 2 中的 SVG 矩形示例

现在让我们在 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> 

AngularJS 2 中的 SVG 矩形示例

自定义元素

假设现在我们希望我们的矩形代码是一个具有自己逻辑的自定义组件。

自定义元素:Angular 2 方式

由于 Angular 2 通过匹配其定义的选择器来呈现组件,因此定义自定义组件非常容易,如下所示:

 @Component({ selector: 'g[custom-rect]', ... })

上面的代码片段会将自定义元素呈现给任何<g custom-rect></div>标签,这非常方便。

自定义元素:Aurelia 方式

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 中,您可以在自定义rect元素上使用@containerless注释。 @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(); } }

如您所见,通过依赖注入,任何类都可以成为完全可扩展的服务,因此您甚至可以编写自己的解析器。

奥里利亚的工厂

如果您需要的是工厂或新实例( FactoryNewInstance只是开箱即用的几个流行的解析器),您可以执行以下操作:

 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])调用中指定它。

请注意,无论您如何注入它,都需要同时指定NumberOperatorNumberGenerator

生成的组件将如下所示:

 @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>一起使用,我们将获得在子组件中呈现的精确嵌入的Yes HTML。

对于多槽嵌入,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> 

Angular2 中的插槽

您可以在自定义标签上使用select ,但请记住,Angular 2 必须知道该标签。

带有 Aurelia 的老虎机

还记得我说过 Aurelia 尽可能遵循 Web 标准吗? 在 Aurelia 中,transclusion 称为 slot,它只是 Web Components Shadow DOM 的 polyfill。 Shadow DOM尚未为 slot 创建,但它遵循 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 中的老虎机

还值得注意的是,在 Aurelia 和 Angular 中,您都可以编译模板部分并动态渲染组件(在 Aurelia 中使用<compose>view-model ,或在 Angular 2 中使用ComponentResolver )。

影子 DOM

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 Universal 在后端渲染 Angular 2,而 Aurelia 将在 2017 年实现这一点,正如其团队的新年决议所述。 事实上,Aurelia 的 repo 中有一个可运行的演示。

除此之外,Aurelia 已经拥有一年多的渐进增强功能,Angular 2 没有,因为它的非标准 HTML 语法。

尺寸、性能和接下来会发生什么

虽然它没有向我们展示全貌,但使用默认配置和优化实现的 DBMonster 基准测试描绘了一幅很好的对比图:Aurelia 和 Angular 显示出相似的结果,每秒大约 100 次重新渲染(在 MacBook Pro 上测试),而 Angular 1 显示了大约一半的结果。 Aurelia 和 Angular 的性能都比 Angular 1 高出大约 5 倍,并且都比 React 领先 40%。 Aurelia 和 Angular 2 都没有虚拟 DOM 实现。

在大小问题上,Angular 大约是 Aurelia 的两倍,但谷歌的人正在努力解决这个问题:Angular 的路线图包括发布 Angular 4,计划使其更小、更轻量级,同时改善开发人员体验。 没有 Angular 3,实际上,在谈论 Angular 时应该删除版本号,因为主要版本计划每 6 个月发布一次。 如果您查看 Angular 从 alpha 到其当前版本的路径,您会发现它并不总是与从构建到构建的属性重命名等事情保持一致。Angular 团队承诺破坏性更改将很容易迁移。

截至 2017 年,Aurelia 团队计划发布 Aurelia UX,提供更多集成和工具,并实现服务器端渲染(这在路线图上已经有很长时间了)。

Angular 2 vs. Aurelia:品味问题

Angular 和 Aurelia 都很好,选择其中一个是个人品味和优先级的问题。 如果您需要更苗条的解决方案,Aurelia 是您的最佳选择,但如果您需要社区支持,Angular 是您的赢家。 这不是“这个框架允许我……吗?”的问题。 因为答案很简单。 它们提供大致相同的功能,同时遵循不同的理念和风格以及完全不同的 Web 标准方法。