Aurelia vs Angular 2: un confronto di codici
Pubblicato: 2022-03-11Angular e Aurelia, discendenti del buon vecchio JavaScript Angular 1, sono agguerriti concorrenti, sviluppati e rilasciati all'incirca nello stesso periodo e con una filosofia simile, ma differiscono in diversi modi chiave. In questo articolo, faremo confronti affiancati di tali differenze nelle funzionalità e nel codice.
Per farla breve, Aurelia è stata creata da Rob Eisenberg, noto come il creatore di Durandal e Caliburn. Ha lavorato nel team di Angular 2 presso Google, ma ha lasciato nel 2014 quando le sue opinioni su come dovrebbe apparire un framework moderno differivano dalle loro.
Le somiglianze persistono anche a un livello più tecnico: i modelli e i componenti (o elementi personalizzati) ad essi associati sono fondamentali per entrambe le app Angular e Aurelia, ed entrambi richiedono un componente root (cioè l'app). Inoltre, sia Angular che Aurelia utilizzano ampiamente i decoratori per la configurazione dei componenti. Ogni componente ha un ciclo di vita fisso a cui possiamo agganciarci.
Quindi qual è la differenza tra Aurelia e Angular 2?
La differenza fondamentale, secondo Rob Eisenberg, sta nel codice: Aurelia è discreta. Quando sviluppi un'app Aurelia (dopo la configurazione), scrivi in ES6 o TypeScript e i modelli sembrano HTML assolutamente sani, soprattutto se confrontati con Angular. Aurelia è una convenzione sulla configurazione e il 95% delle volte starai bene usando le convenzioni predefinite (come la denominazione dei modelli, la denominazione degli elementi, ecc.), Mentre Angular richiede di fornire la configurazione praticamente per tutto.
Aurelia è anche considerata più conforme agli standard, se non altro perché non fa distinzione tra maiuscole e minuscole quando si tratta di tag HTML, mentre Angular 2 lo è. Ciò significa che Angular 2 non può fare affidamento sul parser HTML del browser, quindi ha creato il proprio.
Un altro fattore da considerare quando si sceglie tra i framework SPA è la comunità, l'ecosistema, che li circonda. Sia Angular che Aurelia hanno tutte le basi (router, motore di template, validazione, ecc.), ed è facile ottenere una modalità nativa o utilizzare una libreria di terze parti, ma non sorprende che Angular abbia una comunità più ampia e uno sviluppo più ampio squadra.
Inoltre, sebbene entrambi i framework siano open source, Angular è sviluppato principalmente da Google e non è destinato alla commercializzazione mentre Durandal, Inc., che impiega il core team, sta seguendo il modello di monetizzazione di Ember.js tramite consulenza e formazione.
Aurelia vs Angular: un confronto di codici
Diamo un'occhiata ad alcune delle caratteristiche più notevoli che sottolineano le filosofie dietro ogni framework.
Dopo aver clonato i progetti seed per Angular e Aurelia, abbiamo un'app Aurelia ES6 (è possibile utilizzare rispettivamente Jspm/System.js, Webpack e RequireJS in combinazione con ES6 o TypeScript) e un'app TypeScript Angular (WebPack).
Diamoci dentro.
Associazione dati
Prima di confrontare esempi di lavoro affiancati, dobbiamo dare un'occhiata ad alcune differenze sintattiche tra Aurelia e Angular 2, in particolare nella funzionalità chiave di associazione dei valori dal controller alla vista. Angular 1 utilizzava il "controllo sporco" per tutto, un metodo che scansionava l'ambito delle modifiche. Questo, comprensibilmente, ha causato una serie di problemi di prestazioni. Né Angular 2 né Aurelia hanno seguito quella strada. Invece, usano l'associazione di eventi.
Data Binding in Angular 2
In Angular, leghi i dati con parentesi quadre e usi le parentesi per associare gli eventi, in questo modo:
<element [property]="value"></a> <element (someEvent)="eventHandler($event)"></a>
L'associazione a due vie, per quando si desidera che le modifiche ai dati dell'applicazione si riflettano sulla vista e viceversa, è una combinazione di parentesi quadre e parentesi quadre. Quindi, per l'input legato a due vie, questo funzionerebbe perfettamente:
<input type="text" [(ngModel)]="text"> {{text}}
In altre parole, le parentesi rappresentano un evento mentre le parentesi quadre rappresentano un valore inviato in input.
Il team di Angular ha fatto un ottimo lavoro nel separare le direzioni di rilegatura: al DOM, dal DOM e al bidirezionale. C'è anche molto zucchero sintattico relativo alle classi e agli stili di rilegatura. Si consideri, ad esempio, il seguente snippet come esempio di associazione unidirezionale:
<div [class.red-container]="isRed"></div> <div [style.width.px]="elementWidth"></div>
Ma cosa succede se vogliamo associare dati a due vie in un componente? Considera la seguente configurazione di input di base:
<!-- 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 }}
Nota che, per usare ngModel
, il tuo modulo deve importare FormsModule da @angular/forms
. Ora abbiamo qualcosa di interessante. L'aggiornamento del valore nell'input padre cambia i valori ovunque, ma la modifica dell'input figlio ha effetto solo su quel figlio. Se vogliamo che aggiorni il valore padre, abbiamo bisogno di un evento che informi il genitore. La convenzione di denominazione per questo evento è property name + 'Change'
, in questo modo:
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); } }
L'associazione a due vie inizia a funzionare correttamente subito dopo l'associazione all'evento ngModelChange
:
<!-- child component --> <input [(ngModel)]="text" (ngModelChange)="triggerUpdate($event)">
Che dire dell'associazione una tantum, quando si dice in modo efficace al framework di ignorare i valori associati anche se cambiano?
In Angular 1, abbiamo usato {{::value}} per associare una volta. In Angular 2, l'associazione una tantum diventa complicata: la documentazione dice che è possibile utilizzare l'attributo changeDetection: ChangeDetectionStrategy.OnPush
nella configurazione del componente, ma ciò renderà tutte le associazioni una sola volta.
Data Binding: La Via Aurelia
A differenza di Angular 2, l'associazione di dati ed eventi in Aurelia è davvero semplice. Puoi utilizzare l'interpolazione, proprio come la property="${value}"
, oppure utilizzare uno dei seguenti tipi di binding:
property.one-time="value" property.one-way="value" property.two-way="value"
I nomi sono autoesplicativi. Inoltre, c'è property.bind="value"
, che è la sintassi-sugar che rileva automaticamente se l'associazione deve essere unidirezionale o bidirezionale. Ritenere:
<!-- 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>
Nello snippet sopra, sia @bindable
che @Input
sono configurabili, quindi puoi facilmente cambiare cose come il nome della proprietà associata, ecc.
E gli eventi? Per associare agli eventi in Aurelia, usi .trigger
e .delegate
. Ad esempio, per fare in modo che un componente figlio attivi un evento, è possibile eseguire le seguenti operazioni:
// child.js this.element.dispatchEvent(new CustomEvent('change', { detail: someDetails }));
Quindi, per ascoltarlo nel genitore:
<child change.trigger="myChangeHandler($event)"></child> <!-- or --> <child change.delegate="myChangeHandler($event)"></child>
La differenza tra questi due è che .trigger
crea un gestore di eventi su quel particolare elemento mentre .delegate
aggiunge un listener su document
. Ciò consente di risparmiare risorse, ma ovviamente non funzionerà per eventi senza bolle.
Un esempio di base di Aurelia vs Angular
Ora che abbiamo coperto l'associazione, creiamo un componente di base che esegue il rendering di una grafica vettoriale scalabile (SVG). Sarà fantastico, quindi lo chiameremo awesome-svg
. Questo esercizio illustrerà sia la funzionalità di base che la filosofia per Aurelia e Angular 2. Gli esempi di codice Aurelia di questo articolo sono disponibili su GitHub.
Esempio di rettangoli SVG in Aurelia
Per prima cosa costruiamo il file JavaScript:
// awesome-svg.js import {bindable} from 'aurelia-framework'; export class AwesomeSvgCustomElement { @bindable title; @bindable colors = []; }
Ora per l'HTML.
In Aurelia puoi specificare il template (o usare quello inline) con le annotazioni @template
, @inlineView
o anche @noView
, ma fuori dagli schemi cerca il file .html
con lo stesso nome del .js
file. Lo stesso vale per il nome dell'elemento personalizzato: puoi impostarlo con @customElement('awesome-svg')
, ma in caso contrario, Aurelia convertirà il titolo in dash-case e cercherà una corrispondenza.
Poiché non abbiamo specificato diversamente, l'elemento si chiamerà awesome-svg
e Aurelia cercherà il template con lo stesso nome del file js
(es. awesome-svg.html
) nella stessa 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>
Notare il tag <template>
? Tutti i modelli devono essere racchiusi in un tag <template>
. Vale anche la pena notare che usi ` for … of and the string interpolation
${title}` proprio come fai in ES6.
Ora per utilizzare il componente, dovremmo importarlo in un modello con <require from="path/to/awesome-svg"></require>
oppure, se è utilizzato nell'app, globalizzare la risorsa nella funzione di configurazione del framework con aurelia.use.globalResources('path/to/awesome-svg');
, che importerà il componente awesome-svg
una volta per tutte.
[Nota, se non esegui nessuna di queste operazioni, <awesome-svg></awesome-svg>
verrà trattato come qualsiasi altro tag HTML, senza errori.]
Puoi visualizzare il componente con:
<awesome-svg colors.bind="['#ff0000', '#00ff00', '#0000ff']"></awesome-svg>
Questo rende un insieme di 3 rettangoli:
Esempio di rettangoli SVG in Angular 2
Ora facciamo lo stesso esempio in Angular 2, disponibile anche su GitHub. Angular 2 richiede di specificare sia il modello che il nome dell'elemento:
// 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[] = [] }
La vista è dove le cose si complicano un po'. Prima di tutto, Angular tratta silenziosamente i tag HTML sconosciuti allo stesso modo di un browser: genera un errore dicendo che qualcosa sulla falsariga di my-own-tag
è un elemento sconosciuto. Fa lo stesso per tutte le proprietà che leghi, quindi se avessi un errore di battitura da qualche parte nel codice, attirerebbe grande attenzione perché l'app si arresterebbe in modo anomalo. Suona bene, vero? Sì, perché te ne accorgeresti subito se hai rotto l'app, e no, perché questa è solo una cattiva forma.
Considera questo frammento, che va benissimo in termini di sintassi di associazione:
<svg> <rect [fill]="color"></rect> </svg>
Anche se si legge bene, riceverai un errore del tipo "impossibile eseguire il binding a 'fill' poiché non è una proprietà nota di ':svg:rect'." Per risolvere questo problema, è necessario utilizzare invece la sintassi [attr.fill]="color"
. Nota inoltre che è necessario specificare lo spazio dei nomi negli elementi figlio all'interno di <svg/>: <svg:rect>
per far sapere ad Angular che questo non deve essere trattato come HTML. Espandiamo il nostro 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>
Eccoci. Quindi, importalo nella configurazione del modulo:

@NgModule({ declarations: [ AwesomeSvgComponent ] //... })
Ora il componente può essere utilizzato in questo modulo, in questo modo:
<awesome-svg [colors]="['#ff0000', '#00ff00', '#0000ff']" title="Rectangles"></awesome-svg>
Elementi personalizzati
Supponiamo ora di volere che il nostro codice rettangolo sia un componente personalizzato con una propria logica.
Elementi personalizzati: The Angular 2 Way
Poiché Angular 2 esegue il rendering dei componenti in base a ciò che corrisponde al suo selettore definito, è estremamente facile definire un componente personalizzato, in questo modo:
@Component({ selector: 'g[custom-rect]', ... })
Lo snippet precedente renderebbe l'elemento personalizzato a qualsiasi <g custom-rect></div>
, il che è estremamente utile.
Elementi personalizzati: La Via Aurelia
Aurelia ci consente di creare elementi personalizzati solo modello:
<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>
L'elemento personalizzato sarà denominato rispetto al nome del file. L'unica differenza rispetto alla denominazione di altri componenti è che durante l'importazione, in configure o tramite il tag <require>
, è necessario inserire .html
alla fine. Ad esempio: <require from="awesome-svg.html"></require>
.
Aurelia ha anche attributi personalizzati, ma non servono allo stesso scopo di Angular 2. Ad esempio, in Aurelia, puoi utilizzare l'annotazione @containerless
sull'elemento rect
personalizzato. @containerless
può essere utilizzato anche con modelli personalizzati senza il controller e <compose>
, che sostanzialmente esegue il rendering di elementi nel DOM.
Considera il codice seguente contenente l'annotazione @containerless
:
<svg> <custom-rect containerless></custom-rect> </svg>
L'output non conterrebbe il tag dell'elemento personalizzato ( custom-rect
), ma invece otteniamo:
<svg> <rect ...></rect> </svg>
Servizi
Per quanto riguarda i servizi, Aurelia e Angular sono molto simili, come vedrai nei seguenti esempi. Supponiamo di aver bisogno NumberOperator
che dipende da NumberGenerator
.
Servizi in Aurelia
Ecco come definire i nostri due servizi in 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++; } }
Ora, per un componente, iniettiamo allo stesso modo:
import {inject} from 'aurelia-framework'; import {NumberOperator} from './_services/number-operator'; @inject(NumberOperator) export class SomeCustomElement { constructor(numberOperator){ this.numberOperator = numberOperator; //this.numberOperator.getNumber(); } }
Come puoi vedere, con l'iniezione delle dipendenze, qualsiasi classe può essere un servizio completamente estensibile, quindi puoi persino scrivere i tuoi risolutori.
Fabbriche in Aurelia
Se ciò di cui hai bisogno è una factory o una nuova istanza ( Factory
e NewInstance
sono solo un paio di popolari risolutori forniti immediatamente), puoi fare quanto segue:
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; } }
Servizi angolari
Ecco lo stesso insieme di servizi in 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++; } }
L'annotazione @Injectable
è richiesta e, per iniettare effettivamente un servizio, è necessario specificare il servizio nell'elenco dei provider nella configurazione del componente o nell'intera configurazione del modulo, in questo modo:
@Component({ //... providers: [NumberOperator, NumberGenerator] })
Oppure, non consigliato, puoi anche specificarlo nella bootstrap(AppComponent, [NumberGenerator, NumberOperator])
.
Si noti che è necessario specificare sia NumberOperator
che NumberGenerator
, indipendentemente da come lo si inietta.
Il componente risultante sarà simile a questo:
@Component({ //... providers: [NumberOperator, NumberGenerator], }) export class SomeComponent { constructor(@Inject(NumberOperator) public service){ //service.getNumber(); } }
Fabbriche in Angular 2
In Angular 2, puoi creare factory con l'annotazione di provide
, che viene utilizzata anche per i servizi di aliasing al fine di prevenire conflitti di nomi. La creazione di una fabbrica potrebbe essere simile alla seguente:
let stuffFactory = (someService: SomeService) => { return new Stuff(someService); } @Component({ //... providers: [provide(Stuff, {useFactory: stuffFactory, deps: [SomeService]})] })
Trasclusione
Angular 1 aveva la capacità di includere contenuto, uno "slot", da un modello all'altro utilizzando la trasclusione. Vediamo cosa hanno da offrire i suoi discendenti.
Proiezione di contenuti con Angular 2
In Angular 2, la trasclusione è chiamata "proiezione del contenuto" e funziona allo stesso modo di ng-transclude
; vale a dire, parlando in termini angolari 1, il contenuto trascluso utilizza l'ambito padre. Corrisponderà al tag contenuto trascluso in base al selettore di configurazione. Ritenere:
@Component({ selector: 'child', template: `Transcluded: <ng-content></ng-content>` }) export class MyComponent {}
Puoi quindi utilizzare il componente con <child-component>Hello from Translusion Component</child-component>
e otterremo l'esatto codice Yes
HTML trascluso visualizzato nel componente figlio.
Per la trasclusione multi-slot, Angular 2 ha selettori che puoi utilizzare nello stesso modo in cui faresti per la configurazione di @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>
Puoi utilizzare select
sui tuoi tag personalizzati, ma ricorda che il tag deve essere noto ad Angular 2.
Slot con Aurelia
Ricordi quando ho detto che Aurelia segue gli standard web quando possibile? In Aurelia, la trasclusione è chiamata slot ed è solo un polyfill per Web Components Shadow DOM. Shadow DOM non è ancora stato creato per gli slot, ma segue le specifiche del W3C.
<!-- child --> <template> Slot: <slot></slot> </template> <!-- parent --> <template> <child>${textValue}</child> </template>
Aurelia è stato progettato per essere conforme agli standard e Angular 2 no. Di conseguenza, possiamo fare cose più fantastiche con gli slot di Aurelia, come usare il contenuto di fallback (il tentativo di utilizzare il contenuto di fallback in Angular 2 fallisce con <ng-content> element cannot have content
). Ritenere:
<!-- 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>
Allo stesso modo di Angular 2, Aurelia renderà tutte le occorrenze dello slot in base a una corrispondenza di nome.
Vale anche la pena notare che sia in Aurelia che in Angular, puoi compilare parti del modello e renderizzare i componenti in modo dinamico (usando <compose>
con view-model
in Aurelia o ComponentResolver
in Angular 2).
Ombra DOM
Sia Aurelia che Angular supportano Shadow DOM.
In Aurelia, usa il decoratore @useShadowDOM
e sei pronto per partire:
import {useShadowDOM} from 'aurelia-framework'; @useShadowDOM() export class YetAnotherCustomElement {}
In Angular, lo stesso può essere fatto con ViewEncapsulation.Native
:
import { Component, ViewEncapsulation } from '@angular/core'; @Component({ //... encapsulation: ViewEncapsulation.Native, }) export class YetAnotherComponent {}
Ricordati di controllare se il tuo browser supporta Shadow DOM.
Rendering lato server
Siamo nel 2017 e il rendering lato server è molto trendy. Puoi già eseguire il rendering di Angular 2 sul back-end con Angular Universal e Aurelia lo avrà nel 2017, come indicato nelle risoluzioni per il nuovo anno del suo team. In effetti, c'è una demo eseguibile nel repository di Aurelia.
In aggiunta a ciò, Aurelia ha avuto capacità di miglioramento progressivo per oltre un anno, qualcosa che Angular 2 non ha a causa della sua sintassi HTML non standard.
Dimensioni, prestazioni e cosa verrà dopo
Sebbene non ci mostri il quadro completo, i benchmark DBMonster, con configurazioni predefinite e implementazione ottimizzata, dipingono un buon quadro di confronto: Aurelia e Angular mostrano risultati simili di circa 100 re-render al secondo (come testato su un MacBook Pro), mentre Angular 1 ha mostrato qualcosa intorno alla metà di quel risultato. Sia Aurelia che Angular superano Angular 1 di circa cinque volte, ed entrambi sono il 40% in più rispetto a React. Né Aurelia né Angular 2 hanno un'implementazione DOM virtuale.
Per quanto riguarda le dimensioni, Angular è circa due volte più grasso di Aurelia, ma i ragazzi di Google ci stanno lavorando: la roadmap di Angular include il rilascio di Angular 4 con l'intenzione di renderlo più piccolo e leggero, migliorando al contempo l'esperienza degli sviluppatori. Non esiste Angular 3 e, in realtà, il numero di versione dovrebbe essere eliminato quando si parla di Angular, poiché le versioni principali sono pianificate ogni 6 mesi. Se osservi il percorso intrapreso da Angular dall'alpha alla sua versione attuale, vedrai che non è sempre stato coerente con cose come la ridenominazione degli attributi da build a build, ecc. Il team di Angular promette che interrompere le modifiche sarà facile migrare.
A partire dal 2017, il team di Aurelia prevede di rilasciare Aurelia UX, fornire più integrazioni e strumenti e implementare il rendering lato server (che è sulla roadmap da molto tempo).
Angular 2 vs. Aurelia: una questione di gusti
Sia Angular che Aurelia sono bravi e scegliersi l'uno rispetto all'altro è una questione di gusti e priorità personali. Se hai bisogno di una soluzione più snella, Aurelia è la tua migliore opzione, ma se hai bisogno del supporto della comunità, Angular è il tuo vincitore. Non è una questione di "Questo quadro mi permette di...?" perché la risposta è semplicemente "sì". Forniscono all'incirca le stesse funzionalità mentre seguono filosofie e stili diversi insieme a un approccio completamente diverso agli standard web.