本文概述
Angular和Aurelia是旧的JavaScript Angular 1的后代, 它们是激烈的竞争者, 大约在同一时间开发和发布, 并具有相似的理念, 但在许多关键方面有所不同。在本文中, 我们将对功能和代码方面的差异进行并排比较。
长话短说, Aurelia由Rob Duisal和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主要由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, 你的模块必须从@ angular / forms导入FormsModule。现在我们有了一些有趣的东西。更新父输入中的值会在任何地方更改值, 但修改子输入则只会影响该子。如果我们希望它更新父级值, 则需要一个事件通知父级。此事件的命名约定为属性名称+’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中, 一次性绑定变得很复杂:文档说你可以在Component的配置中使用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在该特定元素上创建事件处理程序, 而.delegate在文档上添加侦听器。这样可以节省资源, 但对于非冒泡事件显然不起作用。
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会将标题转换为大写字母并寻找匹配项。
由于我们没有另外指定, 因此该元素称为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>标记中。同样值得注意的是, 就像在ES6中一样, 对的of使用`和…以及字符串插值$ {title}`。
现在要使用该组件, 我们应该使用<require from =” path / to / awesome-svg”> </ require>将其导入模板中, 或者, 如果在整个应用程序中都使用了该组件, 请在框架的configure函数中全局化资源使用aurelia.use.globalResources(‘path / to / awesome-svg’);, 它将一劳永逸地导入awesome-svg组件。
[请注意, 如果你不执行上述任何一项操作, 则<awesome-svg> </ awesome-svg>将被视为与任何其他HTML标记一样, 没有错误。
你可以通过以下方式显示组件:
<awesome-svg colors.bind="['#ff0000', '#00ff00', '#0000ff']"></awesome-svg>
这将呈现一组3个矩形:
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>
即使它看起来不错, 也会出现类似”无法绑定到”填充”的错误, 因为它不是’: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方式
由于Angular 2通过与其定义的选择器匹配的方式呈现组件, 因此定义自定义组件非常容易, 如下所示:
@Component({
selector: 'g[custom-rect]', ...
})
上面的代码片段会将custom元素呈现给任何<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>
自定义元素将根据文件名进行命名。与命名其他组件的唯一区别是, 在导入时(通过configure或通过<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非常相似, 如以下示例所示。假设我们需要依赖NumberGenerator的NumberOperator。
奥雷利亚服务
以下是在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 {}
然后, 你可以将该组件与Translusion Component </ child-component>中的<child-component> Hello一起使用, 我们将在子组件中获得完全翻译的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>
你可以在自定义标签上使用select, 但是请记住, Angular 2必须知道该标签。
Aurelia插槽
还记得我说过Aurelia尽可能遵循网络标准吗?在Aurelia中, 包络称为广告位, 它只是Web组件Shadow DOM的一个polyfill。 Shadow DOM尚未为插槽创建, 但遵循W3C规范。
<!-- child -->
<template>
Slot: <slot></slot>
</template>
<!-- parent -->
<template>
<child>${textValue}</child>
</template>
Aurelia的设计符合标准, 而Angular 2并非如此。结果, 我们可以使用Aurelia的广告位做更多很棒的事情, 例如使用后备内容(尝试在Angular 2中使用后备内容会失败, 因为<ng-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中, 你都可以编译模板零件并动态渲染组件(在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的回购中有一个可运行的演示。
除此之外, Aurelia拥有超过一年的渐进增强功能, 而Angular 2则因为其非标准HTML语法而没有。
大小, 性能和下一步
虽然它不能向我们显示全部图像, 但是DBMonster基准测试(具有默认配置和优化的实现)可得出很好的对比图像:Aurelia和Angular每秒显示约100次重新渲染的相似结果(在MacBook Pro上进行了测试), 而Angular 1则显示了大约一半的结果。 Aurelia和Angular的性能都比Angular 1高出大约五倍, 并且都比React高40%。 Aurelia和Angular 2都没有虚拟DOM实现。
在大小问题上, Angular的脂肪大约是Aurelia的两倍, 但Google的人员正在研究:Angular的路线图包括发布Angular 4, 并计划使其更小, 更轻巧, 同时改善开发人员的体验。没有Angular 3, 实际上, 谈论Angular时应该删除版本号, 因为计划每6个月发布一次主要版本。如果你查看Angular从alpha到当前版本的路径, 你会发现它并不总是与诸如从构建到构建的重命名属性之类的东西保持一致。Angular团队承诺, 轻松进行更改迁移。
截至2017年, Aurelia团队计划发布Aurelia UX, 提供更多集成和工具, 并实施服务器端渲染(这在很长一段时间内一直在进行中)。
Angular 2 vs. Aurelia:口味问题
Angular和Aurelia都不错, 而选择一个另一个则取决于个人喜好和优先级。如果你需要更苗条的解决方案, Aurelia是你的最佳选择, 但如果需要社区支持, Angular是你的赢家。这不是”这个框架能让我……吗?”的问题。因为答案就是”是”。它们提供了大致相同的功能, 同时遵循了不同的理念和风格以及完全不同的Web标准方法。
评论前必须登录!
注册