Naar kennisoverzicht

Angular 1 vs. Angular 2: comparing the basics

In my current project we are building an AngularJS (1.x) business administration application. A couple of weeks ago we held a developer session in which we showed our colleagues how to start with AngularJS and to write a simple application. The application has a couple of pages and uses an external api to show a list of Star Wars movies. You can click on each of these movies to view some details. Nothing fancy here but we did show the basics of Angular like controllers, services, directives and routing. To start of lightly we were writing this application in pure JavaScript. Afterwards I’ve been rewriting this application to a TypeScript version to show how that could be done. Last weekend I decided it was about time for me to step in the new world, Angular 2! I’ve rewritten the same application in Angular 2 and in this post I will tell you about the differences I’ve encountered. A little warning: at the time of writing the angular version is 2.0.0-beta.15. Since it’s beta, things could have changed and work differently by now. Before I start, both the applications can be found here:

Bootstrapping

In Angular 1 we bootstrapped our application by putting this ng-app directive right on our html tag or our body tag. We gave it the name of the module we created. In Angular 2 you bootstrap your application through code. (You could have done this in Angular 1 as well btw but that's quite uncommon). We import bootstrap from the Angular platform browser and call the bootstrap function and give it the starting component. This component is the parent component for the entire application. In the index.html we have to add some code as well. The quickstart on angular.io gives a nice introduction on this file.

 

[typescript]
// maint.ts
import {bootstrap}    from 'angular2/platform/browser';
import {AppComponent} from './app.component';

bootstrap(AppComponent);

// app.component.ts
@Component({
    selector: 'my-app',
    templateUrl: 'app/app.component.html'
})
export class AppComponent { }
[/typescript]

index.html: [html]

Loading...

[/html]

Components, Controllers and Services

A lot of concepts from Angular 1 are now gone. This makes things a lot easier to understand and it gets a lot easier to start with Angular. There no longer are controllers, services, factories (and a couple of others). Angular 2 applications are build defining a set of components for each UI element, screen and route. An application will always have a root component that contains other components, a component tree. A component is basically a class with a decorator. This decorator provides metadata for the class, which is data for the component. Examples of this data are the templateUrl, directives and providers property. The following shows how a controller in Angular 1 could become a component in Angular 2..

 

[typescript] // Angular 1 module AngularTypescript{ export class StarWarsController { films: Film[]; static $inject = ["StarWarsService"]; constructor(private starWarsService: IStarWarsService) { starWarsService.getFilms().then((data: Film[]) => { this.films = data; }); } } angular .module("AngularTypescript") .controller("StarWarsController", StarWarsController); } // Angular 2 // I won’t go into the details of promises vs. observables here so ignore the .subscribe() import { Component, OnInit } from 'angular2/core'; import { Film, StarWarsService} from './StarWarsService'; @Component({ templateUrl: './app/Films/filmList.html', providers: [StarWarsService] }) export class FilmListComponent implements OnInit { films: Film[]; constructor(private _starWarsService: StarWarsService) { } ngOnInit() { this.films = []; this._starWarsService.getFilms() .subscribe(films => this.films = films); } } [/typescript] In Angular 1 we would create a service, for example a StarWarsService, with a function to return Star Wars movies. We would then register it with our app (for DI) by using module.service(). [typescript] module AngularTypescript { export class StarWarsService { constructor(private $http: angular.IHttpService){ } getFilms(): angular.IPromise { return this.$http.get('http://swapi.co/api/films/').then(function (response: any){ return response.data.results; }) } } angular .module("AngularTypescript") .service("StarWarsService", StarWarsService); } [/typescript] In Angular 2 we define a class with the same function. Both look very similar. (I won't go into the details of promises vs. observable's here so ignore the .map()) A difference here is the @Injectable decorator. With this decorator we tell Angular that this class can have dependencies that need to be injected into its constructor. I’ll show you how to register the service after the following example: [typescript] import { Injectable } from 'angular2/core'; import { Http, Response } from 'angular2/http'; @Injectable() export class StarWarsService { constructor(private _http: Http) { } getFilms() { return this._http.get('http://swapi.co/api/films/') .map((response: Response) => response.json().results); } } [/typescript] We register this service with Angular via the providers. We do this on the root most parent component that is going to need the service. So, if the Movies and Moviedetails component in the image above would need the MovieService, you would register this service via the providers in the movies component. When the Home component needs it as well, you should register the service via the providers in the application component. Here's how you would tell Angular about the service: [typescript] import {StarWarsService } from './Films/StarWarsService'; @Component({ selector: 'my-app', templateUrl: 'app/app.component.html', providers: [StarWarsService] // Tell Angular about the StarWarsService }) export class AppComponent { } [/typescript]

Dependency Injection in Angular 2 works hierarchical. Therefore, you need be to be careful with registering services twice. If you register a service in the application component and in the movie component, you’ll retrieve a different instance of this service in the movie component than you would in the home component without receiving an error (It could actually be useful).

Data Binding

In Angular 2 there a four ways of data binding: interpolation, one-way binding, two-way binding and event binding.

Interpolation

Interpolation got a little easier. Since we have components now, we already have our context. So, there is no need for vm. Anymore. [html] // Angular 1

{{ vm.pageTitle }}

// Angular 2

{{ pageTitle }}

[/html]

One-way binding

In Angular 1 we have ng-bind. That’s basically a long version of interpolation. In Angular 2 we can now bind to every html element property. That looks like this: [html] // Angular 1

 

// Angular 2

 

[/html] Of course this example is just to make the point. You could better use interpolation here. The following example is more interesting. This set’s the div’s border value to a border model property. [html]

{{ car.make }}

[/html] This means there is no longer a need for directives like ng-href or ng-src. Those have been removed from Angular 2.

Event binding

Event binding are things like clicks or on focus. In Angular 1 we would use ng-click, in Angular 2 we take the same property that is on the html element and wrap it with parentheses. So, no more ng-click directive in Angular 2 (as well as allot of others). [html] // Angular 1 // Angular 2 [/html]

Two-way binding (?)

In Angular 1 we would use ng-model on an input element and give it the name of a property on our model. In Angular 2 we have a special directive called ngModel which we can bind to a property. The syntax is kind of special here. It’s also known as ‘banana in a box’. Internally it’s a combination of one-way binding and event binding. The square brackets mean you want to bind to the property on the input html element, the parentheses mean you want to bind to the change event on this property. So, in Angular 2 there actually is no such thing as two-way binding anymore. [html][/html]

Directives

Structural built-in Directives

Structural directives change the structure of the page. The * in front of the name indicates that it is a structural directive rather than a ‘normal’ one. The most used structural directives probably are *ngFor an *ngIf. Those are replacements for ng-repeat and ng-if. The changes are minimal; the local variable is now preceded by a hash and instead of 'in' we now use the keyword 'of'. [html]

  • {{film.title}}

[/html]

Custom directive

Of course you can still write custom directives like you could (and should) in Angular 1. In Angular 2 a directive doesn’t have a template. A component is a directive with a template. So, to add behavior to the dom without a template you can use the @Directive decorator, to write one with a template use @Component. We provide the name to use in our html via the selector property. [typescript] import { Component, Input } from 'angular2/core'; @Component({ selector: 'film-logo', templateUrl: './app/Films/filmLogo.html' }) export class FilmLogo { @Input() height: number; @Input() width:number; constructor() { } } [/typescript]   [html] // used as: [/html] To be able to use this directive in your components html you have to tell your component you want to use it. You do this via the directives property as shown below. Do not forget to import it. [typescript] import {FilmLogo} from './FilmLogo'; @Component({ templateUrl: './app/Films/filmList.html', directives: [FilmLogo], }) export class FilmListComponent implements OnInit {} [/typescript]

Routing

Configuring routes in Angular 1 is done through the $routeProvider. In Angular 2 we use the @RouteConfig decorator. This accepts an array of routes with four properties: path, name, component and useAsDefault. The last is a replacement for .otherwise() in Angular 1. The rest of the properties becomes clear with this example. The /films/… is a special case. This indicates child routing which we will discuss in a bit. [typescript] import {ROUTER_PROVIDERS, ROUTER_DIRECTIVES, RouteConfig} from 'angular2/router'; import {AboutComponent} from './About/AboutComponent'; import {HomeComponent} from './Home/HomeComponent'; import {FilmComponent} from './Films/FilmComponent'; @Component({ selector: 'my-app', templateUrl: 'app/app.component.html', providers: [ROUTER_PROVIDERS, HTTP_PROVIDERS], directives: [ROUTER_DIRECTIVES] }) @RouteConfig([ {path:'/home', name: 'Home', component: HomeComponent, useAsDefault: true}, {path:'/about', name: 'About', component: AboutComponent}, {path:'/film/...', name: 'Film', component: FilmComponent} ]) export class AppComponent { } [/typescript] Besides @RouteConfig there are two directives we use with routing. The first is the RouterOutlet directives. This is used to tell our router where to render the content in the view. The other directive is the RouterLink directive. This directive is used to create links to route to. The argument for router-link is an array with the name as a first element. This indicates the name of the route to navigate to. The other properties are used for child routing. [html] // app.component.html

Angular 2.x demo!

[/html] With child routing we can split up our route definitions and define them in the components where they belong. So, we can define the child route from films, filmsdetails, in our films component. This way we don’t get a long list with all the routes of the entire application. This is also useful when you would grasp a component, with its child’s, and user it in another application. [typescript] // FilmComponent.ts @RouteConfig([ { path: '/', name: 'Film', component: FilmListComponent, useAsDefault: true }, { path: '/:id', name: 'FilmDetail', component: FilmDetailsComponent } ]) [/typescript] [html] // part of FilmListComponent.html

[/html] To use routing we need to import the ROUTER_PROVIDERS, ROUTER_DIRECTIVES and RouteConfig from angular2/router. We use the providers and directives properties of our main component to make them available.

Filters (or should I say Pipes)

Angular 1 filters are helpful for formatting output in your templates. In Angular 2 we have the functionality, but they are now called Pipes. Some of the filters from Angular 1 were removed from Angular 2, like 'filter'. There are a couple of new ones as well like the 'decimal' and 'async pipe'. Here's an example of a custom type. The transform method is used to do any logic necessary to convert the value that is being passed in. The second parameter is the arguments array. We can pass in as many as we like. [typescript] import {Pipe} from "angular2/core"; @Pipe({ name: "search" }) export class SearchPipe { transform(value, [term]){ return value.filter(item => { return item.title.toLowerCase().startsWith(term); }); } } [/typescript] Like directives, we need to use a property on our components metadata to be able to use our pipe. [typescript] @Component({ pipes: [SearchPipe] }) export class FilmListComponent { } [/typescript]

Angular 1 vs. Angular 2

As shown above there are quite some differences between the two versions of Angular and this list is far from complete. Some changes are  bigger and have more impact than others. Concepts like controllers, services, etc. have been removed which makes things much clearer. It also makes Angular 2 much easier and faster to learn compared to Angular 1 if you don't have any experience with Angular at all. Enough concepts remain the same so your knowledge about Angular 1 is still very useful. Which version you should pick when starting with a new application totally depends on your situation. Upgrading from Angular 1 towards 2 seems to be quite a task. Some stuff from Angular 2 is also being back-ported to Angular 1 which should makes this task more easy.