13分钟阅读

Aurelia.与Angular 2 - 代码比较

Alexander是全堆叠开发人员,您将拯救具有严格的时间表救援项目。通常,他更喜欢极端的发展。

棱角 and Aurelia,良好的老javascript角度1的后代,是激烈的竞争对手,在大约同一时间和相似的​​哲学中开发和释放,但在许多关键方面有所不同。在本文中,我们将在特征和代码中的这些差异进行并排比较。

长话短说,奥雷利亚是由Rob Eisenberg创建的,被称为杜兰德和克里伯恩的创造者。他在谷歌的Angular 2队伍上工作,但是在2014年离开时,他对现代框架的观点应该看起来有何不同。

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

所以宫殿和角度2的区别是什么?

根据Rob Eisenberg的说法,关键差异是代码:Aurelia不引人注目。在开发AURELIA应用程序(配置后)时,您在ES6或Cypescript中写入,模板看起来像绝对的HTML,尤其是与角度相比。 Aurelia在配置中进行了“公约”,95%的时间使用默认公约(例如模板命名,元素命名等),而Angular需要您基本上提供一切的配置。

Aurelia.也被认为是符合更多标准,如果只是因为涉及到HTML标签时不区分大小写,而角2则是。这意味着Angular 2不能依赖浏览器的HTML解析器,因此他们创建了自己的。

在选择SPA框架之间选择的另一个因素是社区 - 生态系统。 Angular和Aurelia都有所有基础知识(路由器,模板引擎,验证等),并且可以轻松获得本地模态或使用一些第三方库,但它没有令人惊讶的是,角度具有更大的社区和更大的发展团队。

此外,虽然这两个框架都是开源的,但是Angular主要由Google开发,而无需在雇用核心团队的DuRandal,Inc。遵循以下情况 Ember.js' 通过咨询和培训货币化模型。

Aurelia.与Angular:代码比较

让我们来看看一些最值得注意的功能,这些功能强调了每个框架背后的哲学。

克隆种子项目后 棱角Aurelia.,我们有一个ES6 Aurelia应用程序(你可以使用JSPM / System.js,webpack和Requirejs与ES6或TypeScript)和CypeScript Angular App(WebPack)组合使用。

来吧。

数据绑定

在我们并排工作示例之前,我们必须看一下Aurelia和Angular 2之间的一些语法差异,即在从控制器到视图的绑定值的关键功能中。 Angular 1用于所有内容的“脏检查”,一种扫描变化范围的方法。这是可以理解的,导致了许多性能问题。既不是角度2和Aurelia遵循这种道路。相反,他们使用事件绑定。

棱角 2中的数据绑定

在Angular中,您将数据与方括号绑定并使用括号绑定事件,如下所示:

<element [property]="value"></a>
<element (someEvent)="eventHandler($event)"></a>

双向绑定 - 当您希望应用程序数据的更改反映视图时,反之亦然 - 是Square和括号括号的组合。因此,对于双向绑定的输入,这会很好:

<input type="text" [(ngModel)]="text">
{{text}}

换句话说,括号代表一个事件,而方括号表示被推到输入的值。

角队团队对分离绑定方向的很大作用:到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 }}

Note that, to use ngModel, your module must import FormsModule from @angular/forms. Now we have something interesting. Updating the value in the parent input changes values everywhere, but modifying the child’s input only affects that child. If we want it to update the parent value, we need an event informing the parent. The naming convention for this event is property name + 'Change', like so:

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);
  }
}

The two-way binding starts working properly right after we bind to the ngModelChange event:

<!-- child component -->
<input [(ngModel)]="text" (ngModelChange)="triggerUpdate($event)">

多次绑定怎么样,当你有效地告诉框架来忽略绑定的值,即使它们也会改变?

In Angular 1, we used {{::value}} to bind one time. In Angular 2, one-time binding gets complicated: the documentation says that you can use the changeDetection: ChangeDetectionStrategy.OnPush attribute in the Component’s configuration, but that will make 全部 你的绑定一次性。

数据绑定:Aurelia方式

In contrast to Angular 2, binding data and events in Aurelia is really simple. You can use either interpolation, just like Angular’s property="${value}", or use one of the following binding types:

property.one-time="value"
property.one-way="value"
property.two-way="value"

The names are self-explanatory. Furthermore, there’s property.bind="value", which is syntax-sugar that self-detects whether the binding should be one-way or two-way. Consider:

<!-- 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>

In the above snippet, both @bindable@Input are configurable, so you can easily change things like the name of the property being bound, etc.

What about events? To bind to events in Aurelia, you use .trigger.delegate. For example, to have a child component fire an event, you might do the following:

// child.js
this.element.dispatchEvent(new CustomEvent('change', {
   detail: someDetails
}));

然后,要在父母中倾听:

<child change.trigger="myChangeHandler($event)"></child>
<!-- or -->
<child change.delegate="myChangeHandler($event)"></child>

The difference between those two is that .trigger creates an event handler on that particular element whilst .delegate adds a listener on document. This saves resources, but it obviously won’t work for non-bubbling events.

Aurelia.与Angular的基本示例

Now that we’ve covered binding, let’s create a basic component which renders a scalable vector graphic (SVG). It’ll be awesome, therefore we’ll call it awesome-svg. This exercise will illustrate both basic functionality and philosophy for Aurelia and Angular 2. This article’s Aurelia code examples are available on GitHub..

在Aurelia的SVG矩形例子

让我们首先构建JavaScript文件:

// awesome-svg.js

import {bindable} from 'aurelia-framework';

export class AwesomeSvgCustomElement {
  @bindable title;
  @bindable colors = [];
}

现在为HTML。

In Aurelia, you can specify the template (or use the inline one) with the annotations @template, @inlineView or even the @noView, but out of the box, it searches for the .html file with the same name as the .js file. The same is true for the custom element’s name—you can set it with @customElement('awesome-svg'), but if you don’t, Aurelia will convert the title to dash-case and look for a match.

Since we didn’t specify otherwise, the element will be called awesome-svg 和 Aurelia will search for the template with the same name as the js file (i.e. awesome-svg.html) in the same directory:

<!-- 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>

Notice the <template> tag? All templates need to be wrapped in a <template> tag. Also worth noticing is that you use ` for … of 和 the string interpolation ${title}` just like you do in ES6.

Now to use the component, we should either import it in a template with <require from="path/to/awesome-svg"></require> or, if it’s used across the app, globalize the resource in the framework’s configure function with aurelia.use.globalResources('path/to/awesome-svg');, which will import the awesome-svg component once and for all.

[Note, if you do neither of these, <awesome-svg></awesome-svg> will be treated just like any other HTML tag, without errors.]

您可以使用以下方式显示组件:

<awesome-svg colors.bind="['#ff0000', '#00ff00', '#0000ff']"></awesome-svg>

这呈现了一组3个矩形:

在Aurelia的SVG矩形例子

角度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[] = []
}

The view is where things get a little bit complicated. First of all, Angular silently treats unknown HTML tags the same way a browser does: it fires an error saying something along the lines of my-own-tag is an unknown element. It does the same for any properties you bind, so if you had a typo somewhere in the code, it would attract major attention because the app would crash. Sounds good, right? Yes, because you’d notice right away if you broke the app, and no, because this is just bad form.

考虑此代码段,在绑定语法方面非常精细:

<svg>
  <rect [fill]="color"></rect>
</svg>

Even though it reads fine, you’ll get an error like “can’t bind to ‘fill’ since it isn’t a known property of ‘:svg:rect’.” To fix this, you need to use the syntax [attr.fill]="color" instead. Also note that it’s required to specify namespace in child elements inside <svg/>: <svg:rect> to let Angular know this should not be treated as HTML. Let’s expand our snippet:

<!-- 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="矩形"></awesome-svg>

棱角js. 2中的SVG矩形示例

自定义元素

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

自定义元素:角度2路

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

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

The above snippet would render the custom element to any <g custom-rect></div> tags, which is extremely handy.

自定义元素: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>

The custom element will be named with respect to the file name. The only difference from naming other components is that when importing, either in configure or via the <require> tag, you should put .html at the end. So for example: <require from="awesome-svg.html"></require>.

Aurelia. has custom attributes, too, but they do not serve the same purpose as in Angular 2. For example, in Aurelia, you can use the @containerless annotation on the custom rect element. @containerless can also be used with custom templates without the controller and <compose>, which basically renders stuff into the DOM.

Consider the following code containing the @containerless annotation:

<svg>
  <custom-rect containerless></custom-rect>
</svg>

The output would not contain the custom element tag (custom-rect), but instead we get:

<svg>
  <rect ...></rect>
</svg>

服务

In regards to services, Aurelia and Angular are very similar, as you’ll see in the following examples. Suppose we need NumberOperator which depends on NumberGenerator.

Aurelia.的服务

以下是如何在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();
  }
}

正如您可以看到的,在依赖注入中,任何类都可以是一个完全可扩展的服务,因此您甚至可以编写自己的解压器。

Aurelia.的工厂

If what you need is a factory or a new instance (FactoryNewInstance are just a couple popular resolvers provided out of the box), you can do the following:

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;
  }
}

角度服务

这是一组角度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++;
  }
}

The @Injectable annotation is required, and to actually inject a service, you need to specify the service in the list of providers in the component configuration or the entire module configuration, like so:

@Component({
  //...
  providers: [NumberOperator, NumberGenerator]
})

Or, not recommended, you can also specify it in the bootstrap(AppComponent, [NumberGenerator, NumberOperator]) call.

Note that you need to specify both NumberOperatorNumberGenerator, regardless of how you inject it.

结果组件将看起来像这样:

@Component({
  //...
  providers: [NumberOperator, NumberGenerator],
})
export class SomeComponent {
  constructor(@Inject(NumberOperator) public service){
    //service.getNumber();
  }
}
工厂在角度2

In Angular 2, you can create factories with the provide annotation, which is also used for aliasing services in order to prevent name collisions. Creating a factory might look like the following:

let stuffFactory = (someService: SomeService) => {
    return new Stuff(someService);
}

@Component({
  //...
  providers: [provide(Stuff, {useFactory: stuffFactory, deps: [SomeService]})]
})

爆发

棱角 1具有将内容的内容,一个模板与另一个模板的内容一起包含在另一个模板中。让我们看看其后代必须提供什么。

与角度2的内容投影

In Angular 2, transclusion is called “content projection,” and it works the same way that ng-transclude did; i.e., speaking in Angular 1 terms, the transcluded content uses the parent scope. It will match the transcluded content tag based on the configuration selector. Consider:

@Component({
  selector: 'child',
  template: `Transcluded: <ng-content></ng-content>`
})
export class MyComponent {}

You can then use the component with <child-component>Hello from Translusion Component</child-component>, and we’ll get the exact transcluded Yes HTML rendered in the child-component.

For multi-slot transclusion, Angular 2 has selectors that you can use in the same manner as you would for @Component configuration:

<!-- 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>

棱角2中的插槽

You can use select on your custom tags, but remember that the tag must be known to Angular 2.

奥雷拉队的插槽

请记住,当我说Aurelia随时遵循Web标准?在Aurelia,转换称为插槽,它只是Web组件阴影DOM的Polyfill。阴影DOM不会为插槽创建 然而,但它遵循 W3C规范.

<!-- child -->
<template>
  Slot: <slot></slot>
</template>

<!-- parent -->
<template>
  <child>${textValue}</child>
</template>

Aurelia.被设计为符合标准,并且角2件不是。结果,我们可以 用Aurelia的插槽做更棒的事情, like use fallback content (trying to use fallback content in Angular 2 fails with <ng-content> element cannot have content). Consider:

<!-- 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>

与角度2的时尚,Aurelia将基于名称匹配渲染所有插槽的所有发生。

在Aurelia的老虎机

It’s also worth noting that in both Aurelia and Angular, you can compile template parts and render components dynamically (using <compose> with view-model in Aurelia, or ComponentResolver in Angular 2).

阴影DOM

Aurelia.和角度支持阴影DOM。

In Aurelia, just use the @useShadowDOM decorator and you’re ready to go:

import {useShadowDOM} from 'aurelia-framework';

@useShadowDOM()
export class YetAnotherCustomElement {}

In Angular, the same can be done with ViewEncapsulation.Native:

import { Component, ViewEncapsulation } from '@angular/core';

@Component({
  //...
  encapsulation: ViewEncapsulation.Native,
})
export class YetAnotherComponent {}

记得检查你的浏览器是否 支持 Shadow DOM.

服务器端渲染

这是2017年,服务器端渲染非常时尚。您可以在后端呈现角度2 角普遍而且Aurelia将在2017年在2017年举行 其团队的新年决议。事实上,有 一个可追加的演示 in Aurelia’s repo.

除此之外,Aurelia还有一年多的渐进式增强能力,有角度2不是因为其非标准的HTML语法而不是。

尺寸,性能和下一步是什么

虽然它没有向我们展示DBMONSTER基准,但具有默认配置和优化的实现,涂抹良好的比较图片:Aurelia和Angular展示了每秒大约100个重新渲染的类似结果(如MacBook Pro上所测试),虽然角度1显示了其中一半结果的东西。 Aurelia和角度均匀的角度1乘大约五次,两者在反应前40%。 Aurelia和Angular 2都没有有虚拟DOM实现。

在尺寸问题上,角度大致两倍于肥胖,但谷歌的人正在努力:角度的路线图包括释放角度4,计划在提高开发人员体验时使其更小,更轻质地释放4。没有角度3,而且真的,在谈角时应该删除版本号,因为每6个月计划主要版本。如果看看Anha的路径从Alpha到当前版本,您将看到它并不总是与从Build Build中重命名的物品等物品等。角队团队承诺破坏变化将很容易迁移。

截至2017年,Aurelia团队计划释放Aurelia UX,提供更多的集成和工具,并实施服务器端渲染(长时间已在路线图上)。

棱角 2与Aurelia:味道的问题

棱角和Aurelia都很好,并选择一个以上是个人品味和优先事项的问题。如果您需要更加苗条的解决方案,Aurelia是您的最佳选择,但如果您需要社区支持,则角度是您的赢家。这不是一个问题“这个框架允许我......?”因为答案只是“是的”。它们提供大致相同的功能,同时跟随不同的哲学和风格以及完全不同的Web标准方法。