Aurelia vs. Angular 2 — O comparație de cod
Publicat: 2022-03-11Angular și Aurelia, descendenții vechiului JavaScript Angular 1, sunt concurenți înverșunați, dezvoltați și lansati aproximativ în același timp și cu o filozofie similară, dar care diferă în mai multe moduri cheie. În acest articol, vom face comparații una lângă alta ale acestor diferențe de caracteristici și cod.
Pe scurt, Aurelia a fost creată de Rob Eisenberg, cunoscut drept creatorul Durandal și Caliburn. A lucrat în echipa Angular 2 de la Google, dar a plecat în 2014, când părerile sale despre cum ar trebui să arate un cadru modern au fost diferite de ale lor.
Asemănările persistă și la un nivel mai tehnic: șabloanele și componentele (sau elementele personalizate) asociate cu acestea sunt esențiale atât pentru aplicațiile Angular, cât și pentru Aurelia și ambele necesită să aveți o componentă rădăcină (adică aplicația). În plus, atât Angular, cât și Aurelia folosesc intens decoratori pentru configurarea componentelor. Fiecare componentă are un ciclu de viață fix la care ne putem conecta.
Deci, care este diferența dintre Aurelia și Angular 2?
Diferența cheie, potrivit lui Rob Eisenberg, este în cod: Aurelia este discretă. Când dezvoltați o aplicație Aurelia (după configurare), scrieți în ES6 sau TypeScript, iar șabloanele arată ca HTML absolut sănătos, mai ales în comparație cu Angular. Aurelia este convenție peste configurație și, în 95% din timp, veți fi bine folosind convențiile implicite (cum ar fi denumirea șablonului, denumirea elementelor etc.), în timp ce Angular vă cere să furnizați configurație pentru practic orice.
Aurelia este, de asemenea, considerată a fi mai compatibilă cu standardele, fie și doar pentru că nu face distincție între majuscule și minuscule când vine vorba de etichete HTML, în timp ce Angular 2 este. Aceasta înseamnă că Angular 2 nu se poate baza pe parserul HTML al browserului, așa că și-a creat al lor.
Un alt factor de luat în considerare atunci când alegeți între cadrele SPA este comunitatea - ecosistemul - din jurul lor. Atât Angular, cât și Aurelia au toate elementele de bază (router, motor de șablon, validare etc.) și este ușor să obțineți un mod nativ sau să utilizați o bibliotecă terță parte, dar nu este surprinzător faptul că Angular are o comunitate mai mare și o dezvoltare mai mare. echipă.
În plus, deși ambele cadre sunt open source, Angular este dezvoltat în principal de Google și nu este destinat să fie comercializat, în timp ce Durandal, Inc., care angajează echipa principală, urmează modelul Ember.js de monetizare prin consultanță și instruire.
Aurelia vs. Angular: o comparație de cod
Să ne uităm la unele dintre cele mai notabile caracteristici care subliniază filozofiile din spatele fiecărui cadru.
După clonarea proiectelor seed pentru Angular și Aurelia, avem o aplicație ES6 Aurelia (puteți folosi Jspm/System.js, Webpack și RequireJS în combinație cu ES6 sau TypeScript) și, respectiv, o aplicație TypeScript Angular (WebPack).
Sa mergem.
Legarea datelor
Înainte de a compara exemple de lucru unul lângă altul, trebuie să aruncăm o privire asupra unor diferențe sintactice dintre Aurelia și Angular 2, și anume în funcționalitatea cheie a valorilor de legare de la controler la vizualizare. Angular 1 a folosit „verificarea murdară” pentru orice, o metodă care a scanat domeniul de aplicare pentru modificări. Acest lucru, de înțeles, a cauzat o serie de probleme de performanță. Nici Angular 2, nici Aurelia nu au urmat acest drum. În schimb, folosesc legarea de evenimente.
Legarea datelor în Angular 2
În Angular, legați datele cu paranteze pătrate și utilizați paranteze pentru a lega evenimente, astfel:
<element [property]="value"></a> <element (someEvent)="eventHandler($event)"></a>Legarea în două sensuri – pentru când doriți ca modificările în datele aplicației să se reflecte în vizualizare și invers – este o combinație atât între paranteze pătrate, cât și între paranteze. Deci, pentru intrare legată în două sensuri, aceasta ar funcționa foarte bine:
<input type="text" [(ngModel)]="text"> {{text}}Cu alte cuvinte, parantezele reprezintă un eveniment, în timp ce parantezele pătrate reprezintă o valoare care este împinsă la intrare.
Echipa Angular a făcut o treabă grozavă separând direcțiile de legare: către DOM, de la DOM și bidirecționale. Există, de asemenea, o mulțime de zahăr de sintaxă legată de clasele și stilurile de legare. Luați în considerare, de exemplu, următorul fragment ca exemplu de legare unidirecțională:
<div [class.red-container]="isRed"></div> <div [style.width.px]="elementWidth"></div>Dar ce se întâmplă dacă vrem să legăm date bidirecționale într-o componentă? Luați în considerare următoarea configurație de bază a intrării:
<!-- 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 }} Rețineți că, pentru a utiliza ngModel , modulul dvs. trebuie să importe FormsModule din @angular/forms . Acum avem ceva interesant. Actualizarea valorii din intrarea părinte modifică valorile peste tot, dar modificarea intrării copilului afectează numai acel copil. Dacă vrem să actualizeze valoarea părinte, avem nevoie de un eveniment care să informeze părintele. Convenția de denumire pentru acest eveniment este property name + 'Change' , astfel:
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); } } Legarea în două sensuri începe să funcționeze corect imediat după ce ne legăm la evenimentul ngModelChange :
<!-- child component --> <input [(ngModel)]="text" (ngModelChange)="triggerUpdate($event)">Ce zici de legarea unică, când îi spui efectiv cadrului să ignore valorile legate chiar dacă acestea se schimbă?
În Angular 1, am folosit {{::value}} pentru a lega o singură dată. În Angular 2, legarea unică devine complicată: documentația spune că puteți utiliza changeDetection: ChangeDetectionStrategy.OnPush în configurația componentei, dar asta va face toate legăturile dvs. o singură dată.
Legarea datelor: Calea Aurelia
Spre deosebire de Angular 2, legarea datelor și a evenimentelor în Aurelia este foarte simplă. Puteți utiliza fie interpolarea, la fel ca property="${value}" , sau puteți utiliza unul dintre următoarele tipuri de legare:
property.one-time="value" property.one-way="value" property.two-way="value" Numele se explică de la sine. În plus, există property.bind="value" , care este sintaxă-zahăr care auto-detectă dacă legarea ar trebui să fie unidirecțională sau bidirecțională. Considera:
<!-- 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> În fragmentul de mai sus, atât @bindable , cât și @Input sunt configurabile, astfel încât să puteți schimba cu ușurință lucruri precum numele proprietății care este legată etc.
Dar evenimentele? Pentru a vă lega la evenimentele din Aurelia, utilizați .trigger și .delegate . De exemplu, pentru ca o componentă copil să declanșeze un eveniment, puteți face următoarele:
// child.js this.element.dispatchEvent(new CustomEvent('change', { detail: someDetails }));Apoi, pentru a asculta asta în părinte:
<child change.trigger="myChangeHandler($event)"></child> <!-- or --> <child change.delegate="myChangeHandler($event)"></child> Diferența dintre cele două este că .trigger creează un handler de evenimente pe acel element anume, în timp ce .delegate adaugă un ascultător pe document . Acest lucru economisește resurse, dar în mod evident nu va funcționa pentru evenimentele care nu se vor întâmpina.
Un exemplu de bază de Aurelia vs. Angular
Acum că am acoperit legarea, să creăm o componentă de bază care redă o grafică vectorială scalabilă (SVG). Va fi minunat, prin urmare îl vom numi awesome-svg . Acest exercițiu va ilustra atât funcționalitatea de bază, cât și filozofia pentru Aurelia și Angular 2. Exemplele de cod Aurelia din acest articol sunt disponibile pe GitHub.
Exemplu de dreptunghiuri SVG în Aurelia
Să construim mai întâi fișierul JavaScript:
// awesome-svg.js import {bindable} from 'aurelia-framework'; export class AwesomeSvgCustomElement { @bindable title; @bindable colors = []; }Acum pentru HTML.
În Aurelia, puteți specifica șablonul (sau îl folosiți pe cel inline) cu adnotările @template , @inlineView sau chiar @noView , dar din cutie caută fișierul .html cu același nume ca și .js . fişier. Același lucru este valabil și pentru numele elementului personalizat — îl puteți seta cu @customElement('awesome-svg') , dar dacă nu o faceți, Aurelia va converti titlul în liniuță și va căuta o potrivire.
Deoarece nu am specificat altfel, elementul se va numi awesome-svg și Aurelia va căuta șablonul cu același nume ca fișierul js (adică awesome-svg.html ) în același director:
<!-- 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> Observați eticheta <template> ? Toate șabloanele trebuie să fie împachetate într-o etichetă <template> . De asemenea, merită remarcat faptul că utilizați ` for ... of and the string interpolation ${title}` la fel ca în ES6.
Acum, pentru a folosi componenta, ar trebui fie să o importăm într-un șablon cu <require from="path/to/awesome-svg"></require> sau, dacă este folosită în aplicație, să globalizăm resursa în funcția de configurare a cadrului. cu aurelia.use.globalResources('path/to/awesome-svg'); , care va importa componenta awesome-svg o dată pentru totdeauna.
[Rețineți, dacă nu faceți niciuna dintre acestea, <awesome-svg></awesome-svg> va fi tratată la fel ca orice altă etichetă HTML, fără erori.]
Puteți afișa componenta cu:
<awesome-svg colors.bind="['#ff0000', '#00ff00', '#0000ff']"></awesome-svg>Aceasta redă un set de 3 dreptunghiuri:
Exemplu de dreptunghiuri SVG în Angular 2
Acum să facem același exemplu în Angular 2, disponibil și pe GitHub. Angular 2 necesită să specificăm atât șablonul, cât și numele elementului:
// 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[] = [] } Priveliștea este acolo unde lucrurile se complică puțin. În primul rând, Angular tratează în tăcere etichetele HTML necunoscute în același mod în care o face un browser: declanșează o eroare care spune că ceva de genul my-own-tag este un element necunoscut. Face același lucru pentru orice proprietăți pe care le legați, așa că dacă ați avea o greșeală de tipar undeva în cod, ar atrage o atenție majoră, deoarece aplicația s-ar bloca. Sună bine, nu? Da, pentru că ai observa imediat dacă ai spart aplicația și nu, pentru că aceasta este doar o formă proastă.
Luați în considerare acest fragment, care este perfect în ceea ce privește sintaxa de legare:
<svg> <rect [fill]="color"></rect> </svg> Chiar dacă se citește bine, veți primi o eroare precum „nu se poate lega la „fill”, deoarece nu este o proprietate cunoscută a lui „:svg:rect”. Pentru a remedia acest lucru, trebuie să utilizați în schimb sintaxa [attr.fill]="color" . De asemenea, rețineți că este necesar să specificați spațiul de nume în elementele copil din <svg/>: <svg:rect> pentru a-i spune lui Angular că acest lucru nu ar trebui tratat ca HTML. Să extindem fragmentul nostru:
<!-- 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>Iată-ne. Apoi, importați-l în configurația modulului:

@NgModule({ declarations: [ AwesomeSvgComponent ] //... })Acum componenta poate fi utilizată în acest modul, astfel:
<awesome-svg [colors]="['#ff0000', '#00ff00', '#0000ff']" title="Rectangles"></awesome-svg> Elemente personalizate
Să presupunem că acum dorim ca codul nostru dreptunghiular să fie o componentă personalizată cu propria sa logică.
Elemente personalizate: The Angular 2 Way
Deoarece Angular 2 redă componente după ceea ce se potrivește cu selectorul definit, este extrem de ușor să definiți o componentă personalizată, astfel:
@Component({ selector: 'g[custom-rect]', ... }) Fragmentul de mai sus ar reda elementul personalizat la orice etichetă <g custom-rect></div> , ceea ce este extrem de util.
Elemente personalizate: Calea Aurelia
Aurelia ne permite să creăm elemente personalizate numai pentru șablon:
<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> Elementul personalizat va fi denumit în funcție de numele fișierului. Singura diferență față de denumirea altor componente este că atunci când importați, fie în configurație, fie prin eticheta <require> , ar trebui să puneți .html la sfârșit. Deci, de exemplu: <require from="awesome-svg.html"></require> .
Aurelia are și atribute personalizate, dar nu servesc aceluiași scop ca în Angular 2. De exemplu, în Aurelia, puteți folosi adnotarea @containerless pe elementul rect personalizat. @containerless poate fi folosit și cu șabloane personalizate fără controler și <compose> , care practic redă lucruri în DOM.
Luați în considerare următorul cod care conține adnotarea @containerless :
<svg> <custom-rect containerless></custom-rect> </svg> Ieșirea nu ar conține eticheta de element personalizat ( custom-rect ), dar în schimb obținem:
<svg> <rect ...></rect> </svg>Servicii
În ceea ce privește serviciile, Aurelia și Angular sunt foarte asemănătoare, așa cum veți vedea în exemplele următoare. Să presupunem că avem nevoie de NumberOperator care depinde de NumberGenerator .
Servicii in Aurelia
Iată cum ne definim cele două servicii din 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++; } }Acum, pentru o componentă, injectăm în același mod:
import {inject} from 'aurelia-framework'; import {NumberOperator} from './_services/number-operator'; @inject(NumberOperator) export class SomeCustomElement { constructor(numberOperator){ this.numberOperator = numberOperator; //this.numberOperator.getNumber(); } }După cum puteți vedea, cu injecția de dependență, orice clasă poate fi un serviciu complet extensibil, deci puteți chiar să vă scrieți propriile soluții.
Fabrici din Aurelia
Dacă ceea ce aveți nevoie este o fabrică sau o instanță nouă ( Factory și NewInstance sunt doar câteva soluții populare furnizate din cutie), puteți face următoarele:
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; } }Servicii unghiulare
Iată același set de servicii în 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++; } } Adnotarea @Injectable este necesară, iar pentru a injecta efectiv un serviciu, trebuie să specificați serviciul în lista de furnizori în configurația componentei sau întreaga configurație a modulului, astfel:
@Component({ //... providers: [NumberOperator, NumberGenerator] }) Sau, nerecomandat, îl puteți specifica și în bootstrap(AppComponent, [NumberGenerator, NumberOperator]) .
Rețineți că trebuie să specificați atât NumberOperator , cât și NumberGenerator , indiferent de modul în care le injectați.
Componenta rezultată va arăta cam așa:
@Component({ //... providers: [NumberOperator, NumberGenerator], }) export class SomeComponent { constructor(@Inject(NumberOperator) public service){ //service.getNumber(); } }Fabrici în Angular 2
În Angular 2, puteți crea fabrici cu adnotarea de provide , care este folosită și pentru servicii de alias pentru a preveni coliziunile de nume. Crearea unei fabrici poate arăta astfel:
let stuffFactory = (someService: SomeService) => { return new Stuff(someService); } @Component({ //... providers: [provide(Stuff, {useFactory: stuffFactory, deps: [SomeService]})] })Transcluzia
Angular 1 a avut capacitatea de a include conținut, un „slot”, dintr-un șablon în altul folosind transcluzia. Să vedem ce au de oferit descendenții săi.
Proiecție de conținut cu Angular 2
În Angular 2, transcluzia se numește „proiecție de conținut” și funcționează la fel ca ng-transclude ; adică, vorbind în termeni Angular 1, conținutul transclus folosește domeniul de aplicare parent. Se va potrivi cu eticheta de conținut transclus pe baza selectorului de configurare. Considera:
@Component({ selector: 'child', template: `Transcluded: <ng-content></ng-content>` }) export class MyComponent {} Apoi puteți utiliza componenta cu <child-component>Hello from Translusion Component</child-component> și vom obține codul HTML exact transclus Yes în componenta copil.
Pentru transcluzia cu mai multe sloturi, Angular 2 are selectoare pe care le puteți folosi în același mod ca și pentru configurația @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> Puteți folosi select pe etichetele personalizate, dar rețineți că eticheta trebuie să fie cunoscută de Angular 2.
Sloturi cu Aurelia
Îți amintești când am spus că Aurelia urmează standardele web ori de câte ori este posibil? În Aurelia, transcluzia se numește sloturi și este doar o completare polivalentă pentru Web Components Shadow DOM. Shadow DOM nu este încă creat pentru sloturi, dar respectă specificațiile W3C.
<!-- child --> <template> Slot: <slot></slot> </template> <!-- parent --> <template> <child>${textValue}</child> </template> Aurelia a fost concepută pentru a fi conformă cu standardele, iar Angular 2 nu a fost. Ca rezultat, putem face lucruri mai minunate cu sloturile Aurelia, cum ar fi folosirea conținutului alternativ (încercarea de a folosi conținut alternativ în Angular 2 eșuează cu <ng-content> element cannot have content ). Considera:
<!-- 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>În același mod ca și Angular 2, Aurelia va reda toate aparițiile slotului pe baza unei potriviri de nume.
De asemenea, merită remarcat faptul că atât în Aurelia, cât și în Angular, puteți compila părți șablon și reda componente dinamic (folosind <compose> cu view-model în Aurelia sau ComponentResolver în Angular 2).
Shadow DOM
Atât Aurelia, cât și Angular acceptă Shadow DOM.
În Aurelia, trebuie doar să utilizați decoratorul @useShadowDOM și sunteți gata să începeți:
import {useShadowDOM} from 'aurelia-framework'; @useShadowDOM() export class YetAnotherCustomElement {} În Angular, același lucru se poate face cu ViewEncapsulation.Native :
import { Component, ViewEncapsulation } from '@angular/core'; @Component({ //... encapsulation: ViewEncapsulation.Native, }) export class YetAnotherComponent {}Nu uitați să verificați dacă browserul dvs. acceptă Shadow DOM.
Redare pe partea serverului
Este 2017, iar randarea pe server este foarte la modă. Puteți deja reda Angular 2 pe back-end cu Angular Universal, iar Aurelia va avea acest lucru în 2017, așa cum se menționează în rezoluțiile echipei sale de Anul Nou. De fapt, există un demo care poate fi rulat în depozitul Aureliei.
În plus, Aurelia are capacități de îmbunătățire progresivă de peste un an, ceva ce Angular 2 nu are din cauza sintaxei HTML nestandard.
Dimensiune, performanță și ce urmează
Deși nu ne arată întreaga imagine, benchmark-urile DBMonster, cu configurații implicite și implementare optimizată, prezintă o imagine de comparație bună: Aurelia și Angular arată rezultate similare de aproximativ 100 de redări pe secundă (cum au fost testate pe un MacBook Pro), în timp ce Angular 1 a arătat ceva în jur de jumătate din acel rezultat. Atât Aurelia, cât și Angular îl depășesc pe Angular 1 de aproximativ cinci ori și ambele sunt cu 40% înaintea lui React. Nici Aurelia, nici Angular 2 nu au o implementare Virtual DOM.
În ceea ce privește dimensiunea, Angular este de aproximativ două ori mai gras decât Aurelia, dar băieții de la Google lucrează la asta: foaia de parcurs Angular include lansarea Angular 4 cu planuri de a-l face mai mic și mai ușor, îmbunătățind în același timp experiența dezvoltatorului. Nu există Angular 3 și, într-adevăr, numărul versiunii ar trebui să fie renunțat când vorbim despre Angular, deoarece lansările majore sunt planificate la fiecare 6 luni. Dacă te uiți la drumul pe care Angular a parcurs-o de la alpha la versiunea sa actuală, vei vedea că nu a fost întotdeauna coerent cu lucruri precum redenumirea atributelor de la o construcție la alta etc. Echipa Angular promite că întreruperea schimbărilor va fi ușor. a migra.
Începând cu 2017, echipa Aurelia intenționează să lanseze Aurelia UX, să ofere mai multe integrări și instrumente și să implementeze randarea pe server (care a fost pe foaia de parcurs de foarte mult timp).
Angular 2 vs. Aurelia: O chestiune de gust
Atât Angular, cât și Aurelia sunt bune, iar alegerea una față de alta este o chestiune de gust și priorități personale. Dacă ai nevoie de o soluție mai zveltă, Aurelia este cea mai bună opțiune, dar dacă ai nevoie de sprijin comunitar, Angular este câștigătorul tău. Nu este o întrebare de „Acest cadru îmi permite să...?” pentru că răspunsul este pur și simplu „da”. Ele oferă aproximativ aceeași funcționalitate, în timp ce urmează filozofii și stiluri diferite, împreună cu o abordare complet diferită a standardelor web.
