Yesterday I gave a presentation to the Ionic NYC Meetup (a damn nice group of people!) and needed to quickly build a pretty simple Ionic app to speak to a LoopBack server. Because I wanted something quick and dirty, I just whipped up an Ionic 1 application. I wrapped it earlier than expected, so decided to see if I could switch it to Ionic 2 before the presentation. I was able to finish it, and I thought it might be kind of cool to compare both versions. To be clear, I'm not offering up either app as a "Model" Ionic application, but since they do the exact same thing, I thought it would be cool to share as a comparison. Both code bases are up on GitHub (I'll share the link at the end) so you can download and run for yourself if you want.

So before we begin - let me describe the app. It has a grand total of two screens. The first screen is a list of cats fetched by the LoopBack-powered API. The second screen is a detail page for the cat. And yeah, that's it. As I said, this isn't a terribly complex app.

Let's begin by comparing the initial page for version 1 and version 2. In version 1, this is index.html:


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <title></title>

    <link href="lib/ionic/css/ionic.css" rel="stylesheet">
    <link href="css/style.css" rel="stylesheet">

    <script src="lib/ionic/js/ionic.bundle.js"></script>

    <script src="cordova.js"></script>

    <script src="js/app.js"></script>
    <script src="js/controllers.js"></script>
    <script src="js/services.js"></script>

  </head>
  <body ng-app="starter">

    <ion-nav-bar class="bar-royal">
		  <ion-nav-back-button>Back</ion-nav-back-button>
    </ion-nav-bar>

    <ion-nav-view></ion-nav-view>

  </body>
</html>

Let me focus on what I modified. First, I've got three different JavaScript files. One covers the main application logic (app.js), one the controllers for my views (controllers.js), and one for my services (services.js). Also note I've a nav-bar and nav-view defined here. Ionic is going to replace these items on the fly with each particular view.

Ok, on the Ionic V2 side, technically we have an index.html, but generally you don't (I believe) modify it. Rather I think the closest analog is app.ts:


import {Component} from '@angular/core';
import {Platform, ionicBootstrap} from 'ionic-angular';
import {StatusBar} from 'ionic-native';
import {HomePage} from './pages/home/home';


@Component({
  template: '<ion-nav [root]="rootPage"></ion-nav>'
})
export class MyApp {
  rootPage: any = HomePage;

  constructor(platform: Platform) {
    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      StatusBar.styleDefault();
    });
  }
}

ionicBootstrap(MyApp);

Pretty much the only modification I need to ever worry about here is telling it what my first page is and then setting it as the rootPage. Ok, I dig this so far. I don't know if it is necessarily fair to see v1's index.html is equivalent to v2's app.ts, but I'm going with it for now.

Now let's compare the first view. In V1, this is driven by a few different files. I've got a view (partials/home.html) and the controller (js/controllers.js). My view is singularly focused on the home page, but my controllers file actually has code for all the controllers in the app. Ok, first the view:


<ion-view title="Cat List">

	<ion-content class="padding">

		<ion-list class="list list-inset">

			<ion-item ng-repeat="cat in cats" href="#/cat/{{cat.id}}">
				{{cat.name}}
			</ion-item>

		</ion-list>

	</ion-content>

</ion-view>

And the controller:


.controller('HomeCtrl', ['$scope', 'CatService', function($scope, catService) {

	catService.getCats().success(function(cats) {
		$scope.cats = cats;
	});

}])

We're going to skip the service until later in the post. Now let's compare this to V2. Right away, one thing cooler is that my view (pages/home/home.html) and JS code (pages/home/home.ts) are contained within one folder. I could have built my V1 code like that, but I love that V2 kinda forces the organization. Here's the view:


<ion-header>
  <ion-navbar>
    <ion-title>
      Cats
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content class="home" padding>

  <ion-list inset>
    <ion-item *ngFor="let cat of cats" (click)="selectCat(cat)">
      {{cat.name}}
    </ion-item>
  </ion-list>
</ion-content>

And the code:


import {Component} from '@angular/core';
import {NavController} from 'ionic-angular';
import {CatProvider} from '../../providers/cat-provider/cat-provider';
import {CatPage} from '../cat/cat';

@Component({
  providers:[CatProvider],
  templateUrl: 'build/pages/home/home.html'
})
export class HomePage {

  public cats:Array<Object>;

  constructor(private navController: NavController, public catProvider:CatProvider) {

    this.catProvider.load().subscribe(result => {
        this.cats = result;
    });

  }

  selectCat(cat) {
    this.navController.push(CatPage, {id:cat.id});
  }

}

One thing I want to point out - notice how in V2 I handle my navigation. This was handled in V1 in app.js via the routing system. V2's simpler navigation API is a heck of a lot easier (imo, obviously) than the URL-based routing in Angular 1.

Ok, let's move on the detail view. Here is the view in V1:


<ion-view>

	<ion-nav-title>Cat: {{cat.name}}</ion-nav-title>

	<ion-content class="padding">

		<div class="card">
			<div class="item item-divider">
			{{cat.name}}
			</div>
			<div class="item item-text-wrap">
				<p>
				{{cat.name}} is {{cat.age}} years old with a {{cat.color}} color and is 
				<span ng-if="!cat.friendly">not</span> friendly.
				</p>

				<img ng-src="{{cat.image}}">

			</div>
		</div>

	</ion-content>

</ion-view>

All fairly simple. And here is the corresponding code from controllers.js:


.controller('CatCtrl', ['$scope', '$stateParams', 'CatService', function($scope, $stateParams, catService) {

	catService.getCat($stateParams.catid).success(function(cat) {
		$scope.cat = cat;
	});

}]);

Alright, now let's look at V2. First the view:


<ion-header>

  <ion-navbar>
    <ion-title>Cat: {{cat.name}}</ion-title>
  </ion-navbar>

</ion-header>

<ion-content padding>

  <ion-card>
    <ion-card-header>{{cat.name}}</ion-card-header>
    <ion-card-content>
				<p>
				{{cat.name}} is {{cat.age}} years old with a {{cat.color}} color and is 
				<span *ngIf="!cat.friendly">not</span> friendly.
				</p>

        <span *ngIf="cat.image">
          <img [src]="cat.image" style="max-width:500px;">
        </span>

    </ion-card-content>
  </ion-card>

</ion-content>

And the corresponding JavaScript:


import { Component } from '@angular/core';
import { NavController,NavParams } from 'ionic-angular';
import {CatProvider} from '../../providers/cat-provider/cat-provider';

@Component({
  providers:[CatProvider],
  templateUrl: 'build/pages/cat/cat.html',
})
export class CatPage {

  public cat:Object = {name:""};

  constructor(private nav: NavController, private params:NavParams, public catProvider:CatProvider) {
    this.catProvider.get(params.data.id).subscribe(result => {
        this.cat = result;
    });
  
  }

}

Alright - so the last bits to compare are the service. While they do virtually the same thing, they're written pretty darn differently. Here is the service in V1:


angular.module('appServices', [])
.factory('CatService', function($http,$q) {

	return {
		getCat:function(id) {
			return $http.get('http://localhost:3000/api/cats/'+id);
		},
		getCats:function() {
			return $http.get('http://localhost:3000/api/cats?filter[fields][color]=false&filter[fields][age]=false&filter[fields][friendly]=false');
		}
	};

});

And this is version 2:


import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class CatProvider {

  constructor(private http: Http) {
  }

  get(id) {

    return this.http.get('http://localhost:3000/api/cats/'+id)
    .map(res => res.json())

  }

  load() {

    return this.http.get('http://localhost:3000/api/cats?filter[fields][color]=false&filter[fields][age]=false&filter[fields][friendly]=false')
    .map(res => res.json())

  }
}

Alright so that's it. As I said - not the most complex apps, but I like having two such similar apps in both v1 and v2. Makes the differences really stand out.

You can see the full code yourself here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/v1andv2

Now that I've done a few v2 apps, and just had a chance to compare them, here are some random thoughts, in no particular order.

  • I love the organization of Ionic V2 apps. Currently my V1 apps typically have one big controller and one big service file. All my templates are in a views folder. In V2, everything is contained within it's own individual folder. To be clear, I absolutely could build my Angular 1 apps this way. But I like that Ionic forces (strongly leads) me into a cleaner (imo) organization.
  • Ionic's generate CLI commands are a huge time saver. Be sure you make use of it when playing around - it saves a lot of boilerplate work.
  • I'm still not digging the big new "Observables" feature, and yes yes, I know, they are the new hotness, rah rah rah. I get it. I just don't like em yet. They feel really weird and awkward. To be fair, I felt the *exact* same way about Promises, and shoot, I feel like I just got comfortable using them and now we have this new thing. I'm whining - I admit it - but so far this is the only thing about V2 (specifically, Angular 2) that I'm not terribly happy about. Yet.
  • On the other hand, even though I'm finding a lot of Angular 2 a bit hard to get used to, like (click) and *ngFor, they are slowly making more and more sense and I'm digging those changes more and more as well. As I told someone at the Meetup last night - it's frustrating - but enjoyable at the same time!
  • I'm also finding TypeScript a bit difficult at times, but I'm loving it too.