📜 ⬆️ ⬇️

Angular 2 versus Aurelia side by side

image

Not so long ago, an important event happened in the world of web development - Angular 2 beta was released. And it is already possible to speculate on how it will look after release.

But the assessment itself, in a vacuum, resembles the choice of electronics for the manufacturer's advertising booklets. All the chips that are in the gadget are mentioned. And all that are not - are not mentioned and we can not even think about their existence. Therefore, it is much more efficient to evaluate comparing with something else.
')
So the idea was born to compare Angular 2 with a new, but very ambitious project Aurelia , which also recently went into beta. And at the same time to replenish the Habr piggy bank with information about this framework, since so far it is much less than information about Angular 2.

Code examples from the article in the form of ready-to-run projects posted on github . The examples are written in TypeScript and both use systemjs . Systemjs configurations were taken from the quick start guide for each framework and are quite different. It seemed to me reasonable to leave them in the form in which the authors of the frameworks provided them, and not to try to make them similar. I also note that the http-server used in the project does not support pushState (I hope it will support soon, because there is an approved pull request), and therefore I had to include hash based routing in Angular.

We will not compare thoroughly every architectural feature, but rather evaluate frameworks from the point of view of the end user, who simply wants to understand what kind of buns he will receive. It’s impossible to compare everything in one article, so this article will be a kind of familiarity with the overall structure. And more difficult questions will remain on the second part.

We will follow the following scenario:


What is Aurelia and why it is legitimate to compare it with Angular


Since on the same Habré information on Aurelia quite a bit , we can assume that not every reader has heard about it. Therefore, I will tell the background to the creation of Aurelia. Moreover, it is quite interesting and even a bit dramatic.

Aurelia is a project of Rob Eisenberg , author of the highly popular MV * framework for Caliburn.Micro XAML platforms. Later he developed an MV * -web framework for the web, called Durandal . Durandal did not become super popular, but, nevertheless, there were very interesting and elegant solutions in it and the framework gathered its audience of adherents who loved it very much.

But Rob Eisenberg understood all the flaws of Durandal, so along with his support, he was engaged in the development of the so-called NextGen framework .

In January 2014, at the ngConf conference, John Papa, notorious in the world of web development, shared with Angular team manager Brad Green ideas that were laid by Rob Eisenberg in the Durandal and in the NextGen framework. These ideas interested Green and he decided to talk with Eisenberg.

When they met, Brad Green and Rob Eisenberg realized that their views on the future of the web and web development were very similar, and they decided to join forces and Eisenberg began working in the Angular team on the second version of the framework.

However, ten months later, he decided to leave the Angular team , since the direction of development of Angular 2, in his opinion, changed too much and differed from Angular 2, which he agreed to work with.

In a short time, Eisenberg put together a fairly large team, which included stars such as Scott Allen , and returned to work on the framework of his dream. The result of this work was Aurelia .

The public accepted the framework with interest (as the simplest way to evaluate, at the time of writing, Aurelia collected 5,000 stars on github versus 8,000 from Angular 2 ).

The general principles inherent in Angular 2 and Aurelia are very similar - they are players of the same class. But the vision in detail and the range of possibilities are quite different in them, which makes their comparison interesting.

Pros and cons of Angular 2 and Aurelia bird's-eye view


From the tangible characteristics that can be compared when choosing a framework, let's look at perfomance. Aurelia shows interesting results in the dbmonster benchmark, knocking out slightly better points than Angular 2 , and noticeably better than React and Angular1 .
What is dbmonster
Dbmonster is the rendering benchmark, which was developed by Ryan Florence . To assess the speed of work, an array of data is being rendered that is constantly updated and this makes it possible to estimate the speed of the framework. The test was originally written for Angular 1, Angular 2, and React. Later, one of the developers of Aurelia, Jeremy Danyow, presented an implementation for Aurelia .
When evaluating a benchmark, it is useful to pay attention to the following points:
  • Smooth scroll - the page should scroll without "jumping"
  • Tooltips - if you hover the mouse over the list, the tooltip is drawn. It should be drawn smoothly and data should be displayed without delay.
  • Repaint rate and memory rate - in the lower right corner there are two indicators. The first shows the number of redraws per second, the second shows the memory consumption
  • The rate of change of data - at the top of the page there is a slider that allows you to adjust the frequency of change of data. If the decrease in the rate of change of data does not increase the repaint rate, this means that the framework in question ineffectively tracks changes and makes decisions about updating the DOM.

In order for all indicators to work correctly and to get the most “clean” result, it is recommended to use the chrome browser and launch it with the following command:
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --user-data-dir="C:\chrome\dev-sessions\perf" --enable-precise-memory-info --enable-benchmarking --js-flags="--expose-gc" 



From intangible characteristics, I will try to evaluate the strengths and weaknesses of both frameworks (carefully, IMHO author).
The main advantages of Angular


The main advantages of Aurelia


The main cons Angular


The main cons of Aurelia



Angular 2 and Aurelia features, terms and what to do with


The architecture of Angular 2 and Aurelia are very similar. Below I will try in a couple of paragraphs to formulate the principles of work of both, indicating in italics the basic terms and constructions that it makes sense to consider and compare. I hope this will turn out to be readable text, and not a mash-up of terms.

The basis of the application both on Angular 2 and Aurelia are the components associated with the corresponding template .
There must be a root component, which personifies the application ( app ). Metadata can be attached to components with the help of decorators .

Components are initialized using dependency injection . Also, each component has a declared life cycle , which can be embedded with lifecycle hooks . Components can be organized in a hierarchical structure.

State synchronization and communication between the component and the template is performed using data binding . The template rendering process can be embedded into the final HTML using pipes (Angular) or value converters + binding behaviors (Aurelia).

Routing is used to navigate between isolated application areas. Events can be used for communication between application modules.

And finally, go to the examples.

Create the first component


Angular 2

Start by creating the simplest component that will be the root component.
 import {Component} from 'angular2/core'; @Component({selector: 'angular-app', templateUrl: 'app/app.html'} }) export class App { message: string = 'Welcome to Angular 2!'; } 


In order for Angular to understand that our module is a component, you need to wrap it with @Component decorator.

Note - strictly speaking, @Component is not a decorator, but an annotation. You can read about the difference here . In the article, we leave the term decorator, because in the documentation for Angular 2 annotations are in the section Decorators.

At a minimum, the decorator must specify the selector where to draw the template. In this case, this is the <angular-app> element.

There are two options for the template declaration:
  1. Specify the html string as the template parameter of the @Component decorator. This approach is useful in case the template is compact and you don’t want to make a separate html page for it
  2. Specify the url of the template as the templateUrl parameter. We will need a separate page, so we will use this option.


The template will look like this:
  <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="#">{{message}}</a> </div> </div> 


That's the whole component. Simple enough.
What is good is that the code is cleaner and freer from the constructions of the framework itself than it was in Angular 1. This is good news.

What is embarrassing is the need to use decorators even for the simplest component. Quote from the documentation:
Each Angular component requires a single @Component and at least one View annotation. The @Component annotation specifies when it is instantiated, and

In this case, the parameters that are configured in this example, in most developers, in any case, are subject to some kind of logic, and instead of explicit configuration, conventions could be used, and decorators could be connected for more complex scenarios.

Aurelia

Since Aurelia is built based on conventions, a component is a regular module without any metadata:
 export class App { message: string = "Welcome to Aurelia!"; } 

According to the standard convention, for the template declaration we need to create an html file with the same component name, that is, for our example, this will be the app.html file:
 <template> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="#">${message}</a> </div> </div> </template> 


Each template in Aurelia must be wrapped in a template element. To create an inline template, by analogy with Angular 2, you can use the inlineView decorator. Also in Aurelia, you can change the convention and perform additional settings. Details can be found here .

What is good - the code is extremely clean and clear. No framework constructions.

What is embarrassing - to set up you may need a lot of annotations that solve similar issues. For example, these are inlineView, noView, useView, and useViewStrategy. Documentation is not yet abundant, and there is not even a search in it, so there is a risk of just getting confused about what and where to use.

Configure routing


In our case, each application will have several pages on which the aspects identified in the article will be considered. We will not consider nested routing, since in both Angular and Aurelia, it is essentially the same as routing itself.
Angular 2

In order to configure routing in Angular 2, we need to import the @RouteConfig decorator, import modules that will be tied to routes, and declare our routing map. We do this in our app component:
 ... import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router'; import {BindingSample} from './binding-sample/binding-sample'; import {ComponentSample} from './component-sample/component-sample'; ... @RouteConfig([ { path: '/component-sample', name: 'ComponentSample', component: ComponentSample, useAsDefault: true }, { path: '/binding-sample', name: 'BindingSample', component: BindingSample } ]) export class App { ... } 


For each route we specify:
  1. route pattern
  2. the name of the route (you will need it for binding in the markup)
  3. the module of the component that Angular will create when the route is activated
  4. optional parameter useAsDefault, indicating that this is the default route

If you are confused by the need to load all modules that have a route at the start, then you can use asynchronous loading. To do this, instead of the component parameter, we use the loader parameter, specifying a function that returns a promise that imports the required module. We'll do that, but the sample code is a bit later.

To use routing in templates for drawing navigation and specifying the section where to draw the current component, we need to additionally import the ROUTER_DIRECTIVES directives collection and add them to the directives parameter of the @Component decorator. Total, the app.ts module turns out like this:

 import {Component} from 'angular2/core'; import {View} from 'angular2/core'; import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router'; @Component({ selector: 'angular-app', templateUrl: 'app/app.html', directives: [ROUTER_DIRECTIVES] }) @RouteConfig([ { path: '/component-sample', name: 'ComponentSample', loader : () => System.import('app/component-sample/component-sample').then(m => m.ComponentSample), useAsDefault: true }, { path: '/binding-sample', name: 'BindingSample', loader : () => System.import('app/binding-sample/binding-sample').then(m => m.BindingSample) } ]) export class App { message: string = "Welcome to Angular 2!"; } 


Now add the navigation to app.html. To do this, we use the routerLink directive, passing an array as a parameter, the first element in which is a string with the name of the route that we specified when setting up @RouteConfig.
 … <div id="navbar" class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li class="active"> <a [routerLink]="['ComponentSample']">Component sample</a> </li> <li> <a [routerLink]="['BindingSample']">Binding sample</a> </li> </ul> </div> ... 


The parameter is an array for the case of nested routing, then we pass an array of several lines with the names of the routes.

If the route needs to be parametrized, then an object with parameters is passed to the last element in the array:
 ... <a [routerLink]="['BindingSample', {someParameter: 'someString'}]">Binding sample</a> ... 

And the last moment. We need to specify in the markup the area where the templates for the current route will be drawn. We do this using the router-outlet directive in the same app.html:
 <div class="container"> <router-outlet></router-outlet> </div> 


What is good - new routing is more pleasant than routeProvider, who lived in Angular 1.x, but still raises a number of questions. The absolute majority of types relating to routing in the documentation do not yet have a description, so for now it is difficult to say something definitively.

What confuses - confuses a few things. First of all, it is the need to configure through the decorator - if in our application, for example, there are 50 routes, then the code of our module will simply be lost in all these settings. And if we need any if-logic when building a navigation scheme, then our code runs the risk of becoming a nightmare.
Secondly, this is the lack of explicit access to the entire collection of routes, which could be simply iterated in the markup to render all the navigation, rather than manually rendering each link (which we forget to do). Again, if you have if-logic to build routes, we will have to duplicate this logic in the template so as not to draw too much.

Aurelia

According to the convention in Aurelia, to configure routing for a component, we need to implement the configureRouter method, which Aurelia will call automatically. The same is true for nested routing — any component that has a configureRouter method will form a routing scheme.
 export class App { message: string = "Welcome to Aurelia!"; router: any; configureRouter(config, router) { config.title = 'Welcome to Aurelia!'; config.map([ { route: ['', 'component-sample'], moduleId: 'app/component-sample/component-sample', nav: true, title: 'Component sample' }, { route: 'component-sample', moduleId: 'app/binding-sample/binding-sample', nav: true, title: 'Binding sample' } ]); this.router = router; } } 

For each route we specify:
  1. route pattern (or a set of patterns, a blank line pattern means the default route).
  2. the identifier of the module to initiate when the route is activated.
  3. optional property title - when the route is activated, its value will be added to the page title.
  4. optional nav property - it tells whether the route should go to the navigation model. If you specify a number, it will indicate the order of the element in the navigation model.

Based on the transferred configuration, the Aurelia will compose a navigation model, which in the template can be iterated and rendered navigation:

  <div id="navbar" class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li repeat.for="row of router.navigation" class="${row.isActive ? 'active' : ''}"> <a href.bind="row.href">${row.title}</a> </li> </ul> </div> 


The last step is to specify in the markup the area where the templates for the current route will be drawn. We do this using the router-view directive in the same app.html

  <div class="container"> <router-view></router-view> </div> 


What is good - the setting is simple and quite obvious. It's nice that Aurelia is trying to help us and is building a collection for navigation.

What is embarrassing - compared with the navigation in Durandal, the ability to add arbitrary properties to the route settings was removed. On the one hand, this, of course, is correct, for it is a fig. On the other hand, it greatly reduces the likelihood of using the navigation model in the direction of manual rendering. The navigation model will be useless if you want to add to the menu items not only the title, but also, for example, tooltips.

Add nested components


Angular 2

For an example of working with nested components, there is a component-sample folder in the test project, it contains everything we need.
So, to create a nested component in Angular 2, we need:

  1. Declare a component and bind metadata to it. To initialize properties with data from the parent component, we add the inputs parameter in the @Component decorator, passing it the names of the corresponding properties:
     import {Component} from 'angular2/core'; @Component({ selector: 'test-child-component', inputs: ['inputMessage'], template: `<div class="panel panel-default"> <div class="panel-heading">Child component title</div> <div class="panel-body"> Message from parent component is: {{inputMessage}} </div> </div>` }) export class TestChildComponent { inputMessage: string } 

  2. We import the child component in the parent component (component-sample.ts) and pass it to the directives array of the @Component decorator.
  3. In the template of the parent component we place the element corresponding to the specified selector from the nested component and passing the parameters


As a result, our parent component looks like this:
 import {Component} from 'angular2/core'; import {TestChildComponent} from './test-hild-omponent'; @Component({ template: ` <div class="sample-header"> <h1>{{message}}</h1> </div> <test-child-component [inputMessage]="messageForChild"></test-child-component>`, directives: [TestChildComponent] }) export class ComponentSample { message: string = 'This is a component with child component sample'; messageForChild: string = 'Hello to child component!'; } 


What is good - in general, everything is simple and clear.

What is embarrassing - not enough nice buns, which are in Aurelia. They are described below.

Aurelia

In Aurelia, you can render an embedded component in two ways: custom element and composition.

The first method, in general, is similar to Angular 2 and is used to create complex controls, etc. In the test project code, this approach is demonstrated in the test-custom-element.html file.

The second one is mainly used for master-detail scripts and is more flexible, since we can dynamically specify which component to load, which template to draw and which data to transfer. This approach is demonstrated in a test project in the file test-child-componentponts.

Let us consider both options in turn.

Option 1 - custom element:

To create a nested component using the custom element, we need:
  1. Create a regular component, additionally marking the @bindable properties with the decorator, the values ​​for which we will pass through the template as parameters (by analogy with the inputs parameter in Angular 2). Additionally, if our element is simple and has no behavior, then we can do without creating the component, but simply create a template and list the bindable properties in it using the attribute of the same name. The Aurelia component will create on the fly:
     <template bindable="message"> <div class="panel panel-default"> <div class="panel-heading">Custom element title</div> <div class="panel-body"> Message from parent component is: ${message} </div> </div> </template> 

  2. Add a require element to the template of the parent component that points to the module we need (similar to the directives parameter in Angular 2, but in the template) and draw a custom element in the markup where we need it. Parameters are passed using the attribute <property name> .bind. As a result, the template of the parent component will look like this:
     <template> ... <require from="app/component-sample/test-custom-element.html"></require> <test-custom-element message.bind="messageForCustomElement"></test-custom-element> </template> 


If the item is used everywhere, then it can be registered as a global resource. This will allow you not to write in each <require ...> template. How to do this is written here in the “Making Resources Global” section.

Another nice option that Aurelia has that I couldn’t find in Angular is to transfer the markup from the parent template. If in the parent template inside the test-custom-element element declare the markup, and in the child add the element
 <content></content> 

then the markup from the parent template will be drawn in the child. Also, when working with custom elements, you can use the previously mentioned template parts and declare several replaceable areas.

What is good is simple and logical.
What is confusing - perhaps not everyone will like the need to declare dependencies in the markup.

Option 2 - ompose:

To create an embedded component using composition, we need:
  1. Create a regular component. Since compose assumes a weak component connectivity, we need to use lifecycle hooks for data transfer (they are also available in Angular 2, but about them in the next article). In this case, we use the activate method. We will make the template inline because it is small:
     import {inlineView} from 'aurelia-templating'; @inlineView('<template><h3>${inputMessage}</h3><template>') export class TestChildComponent { inputMessage: string; activate(inputMessage: string) { this.inputMessage = inputMessage; } } 

  2. In the template of the parent element we place the element compose, in which we indicate the identifier of the module with the child component. To transfer data from the parent component to the child, use the attribute model.bind:
     <compose model.bind="messageForChild" view-model="app/testChildComponent"></compose> 


What is good - in my opinion compose is a very cool way to deal with the complexity of the project. It provides insulation between the components while giving greater flexibility.

What is confusing - I have been thinking about this point for a long time, but I could not write anything. I have nothing to complain about.

Data binding


Angular 2

The main work that the Angular team carried out when developing a new data binding, is minimizing the number of directives used and a clear separation of the direction of data movement: to the DOM, from the DOM, both directions. Let's take a look at everything in turn.

To the dom

Unidirectional binding to the DOM is performed using two forms:
string interpolation:
 <div class="panel-body"> This string is builted with {{interpolationString}} syntax </div> 

and property binding:
 <img [src]="iconUrl" /> 

In the case of string interpolation, the text enclosed in double angle brackets is evaluated as an expression that will be executed and its result will be placed in the template. That is, you can bind not only the properties of the component, but also write expressions of this type:
 <h1>2 = {{1+1}}</h1> 

However, it is recommended not to abuse this possibility and to put complex expressions in the properties of components.

In the case of property binding, the key brackets are the square brackets that signal the template engine that this is the name of the property to which you want to assign the result of the expression to the right. Expressions are also supported in this form, but it is not recommended to abuse them. , — routerLink, textContent ..

— Angular 2, , , HTML, Angular 2. . , , :
 <input value="some text" /> 

«» HTML, , , :
 <input [value]="'some text'" /> 



, , class, style, attr. For example, like this:
 <img [style.width]="iconWidth" [style.height]="iconHeight" [src]="iconUrl" /> 

ngClass ngStyle. , .

property binding attribute binding , html DOM- . , Angular 2. Attribute binding , html DOM-element.

From the DOM

, :
 <input type="button" (click)="onClicked()" value="Click me!" /> 

, ( “on”). binding- $event, , , DOM event. inline . , , .

Both directions

, , from the DOM to the DOM:
 <input [value]="twoWayBindedProperty" (input)="twoWayBindedProperty=$event.target.value" /> 

, , Angular ngModel, , , from the DOM to the DOM :
 <input [(ngModel)]="twoWayBindedProperty" /> 


If necessary, you can decompose the whole thing into separate parts, then for the direction from the DOM we need the ngModelChange directive:
 <input [ngModel]="twoWayBindedProperty" (ngModelChange)="twoWayBindedProperty=$event"> 


Note: if you are confused by the syntax with [], () b [()] and you prefer the canonical version, then you can use the prefixes bind-, on- and bindon-, respectively. For example:
 <img bind-src="iconUrl"> 


What is good - despite the fact that the syntax is, to put it mildly, unusual and there have already been a lot of negative comments about him, I personally like it. First, it is very noticeable. Secondly, it is very easy to remember. Thirdly, he will be good friends with the intellisense of various editors, since from the very first character it is clear that this is an Angular 2 design and we immediately get the options for directives and do not have to memorize all these ngClass, ngModel, etc. Fourth, it can be changed to the canonical form, if you really disgusted by all these brackets.

What is embarrassing - in contrast to Aurelia, there is not enough opportunity for one-time binding and event delegation. But these things, in general, cannot be called critical.

Aurelia

Aurelia data binding . , . :

String Interpolation

 <div class="panel-body"> This string is builted with ${interpolationString} syntax </div> 

string interpolation Angular 2, (${} {{}}):

Property binding

C < >.< > = “”. :

Clearly, the syntax for property binding, in general, is as follows:
 <input value.bind="iconUrl" /> 


In addition to binding standard properties, as in Angular 2, there are custom attributes innerHTML, textContent, style. Two spellings are possible - with the addition of .bind and the indication of a property, or without .bind and using string interpolation:
 <div innerhtml.bind="htmlProperty"></div> <div innerhtml="${htmlProperty}"></div> 


Event handlers

To bind event handlers, we have two options:

To access the event inside the handler expression, as in Angular 2, the $ event variable is available.

Note: In his speech at NDC London, Eisenberg said that the Aurelia syntax can be easily rewritten using plugins and demonstrates data binding in Aurelia using the Angular 2 syntax. However, I could not find this plugin anywhere.

What is good is the ability to fine tune if you need it. Binding option one time. Full compatibility with HTML standards.

What is confusing is the rather verbose syntax for the one-time, one-way, and two-way options. And intellisense support will not be as cool as in Angular.

Data binding controls


Angular

Control structures (for, if, switch)

We will not go into details, because, I believe, the reader has already begun to tire. Just look at the general syntax:

 <div class="panel-body"> Select something: <select [(ngModel)]="selectedClass"> <option *ngFor="#alertClass of alertClasses" [value]="alertClass">{{alertClass}}</option> </select> </div> <div class="panel-body"> <div [ngSwitch]="selectedClass"> <template [ngSwitchWhen]="'success'"> <div class="alert alert-success" role="alert">You will be successfull if you learn Angular</div> </template> ... <template ngSwitchDefault>You must choose option</template> </div> <div *ngIf="selectedClass=='success'"> <div class="alert alert-success" role="alert">Extra message with *ngIf binding</div> </div> </div> 


In general, nothing unusual.What you should pay attention to is the syntax ngFor and ngIf - directives are preceded by the * character. As explained in the documentation, this is a kind of syntactic sugar that avoids wrapping templates into a template element.

Creating local variables in templates


Local variables are needed to access data from different areas of the html-template. We have already seen the simplest example of creating a variable in the ngFor loop:
 <option *ngFor="#alertClass of alertClasses" [value]="alertClass">{{alertClass}}</option> 


With the help of the special character #, we specified Angular that we declare a variable to which we will refer inside the template. You can also create a variable indicating the index of the current element of the array:
 <option *ngFor="#alertClass of alertClasses, #index=index" [value]="alertClass">{{index}} {{alertClass}}</option> 


# ( “val-”) ngFor. , html . , , . , watch :
 <input #i placeholder="Type something"> <input type="button" class="btn btn-success" (click)="displayTextboxValue(i.value)" value="And click" /> <br/> But templating doesn't work with it - {{i.value}} 

Aurelia

(for, if, show)

, Aurelia switch ( ). , , Aurelia data binding, HTML extensions, compose .

, repeat, if show:
 <div class="panel-body"> Select something: <select value.bind="selectedClass"> <option repeat.for="alertClass of alertClasses" value.bind="alertClass">${alertClass}</option> </select> </div> <div class="panel-body"> <div if.bind="selectedClass=='success'" class="alert alert-success" role="alert">You will be successfull if you learn Aurelia</div> ... <div show.bind="selectedClass=='success'"> <div class="alert alert-success" role="alert">Extra message with show extension</div> </div> </div> 

Additionally, I note that when writing expressions inside repeat, the following variables are available:


Creating local variables in templates

The syntax for creating local variables inside repeat is already seen above. For comparison with Angular 2, it remains to see how to create variables that point to HTML elements. For this, the ref attribute is used. The created variable can be used both in event handlers and in the template, that is, the watch is executed:

 <div class="panel-body"> <input ref="i" placeholder="Type something"> <input type="button" class="btn btn-success" click.delegate="displayTextboxValue(i.value)" value="And click" /> <br/> And templating works with it - ${i.value} </div> 

— , Angular 2 « — ». , . , , . , if — .
.

That's all. :)

Source: https://habr.com/ru/post/276649/


All Articles