Walkthrough to upgrade an Angular 1.x component to Angular 2

In this article we’re going to look at upgrading your first Angular 1.x component, a simple todo app, across to Angular 2 code. We’ll compare the API differences, templating syntaxes and hopefully it’ll shed some light on upgrading to Angular 2, and well as making it appear less daunting.

Please note: Angular 2 is still in “alpha” state, so APIs/conventions may be subject to change, however I will aim to keep this article and the working code updated.

Angular 1.x Todo App

We’ll be rewriting this small component in Angular 2, so let’s look at the existing functionality:

  • Add items to todo list
  • Ability to delete items
  • Ability to mark items as complete
  • Display count of incomplete and total todos

Let’s look at the source code to understand exactly how it’s built and what’s going on.

The HTML is extremely simple, a <todo> element.

<todo></todo>

And JavaScript Directive:

function todo() {
  return {
    scope: {},
    controller: function () {
      // set an empty Model for the <input>
      this.label = '';
      // have some dummy data for the todo list
      // complete property with Boolean values to display 
      // finished todos
      this.todos = [{
        label: 'Learn Angular',
        complete: false
      },{
        label: 'Deploy to S3',
        complete: true
      },{
        label: 'Rewrite Todo Component',
        complete: true
      }];
      // method to iterate the todo items and return
      // a filtered Array of incomplete items
      // we then capture the length to display 1 of 3
      // for example
      this.updateIncomplete = function () {
        return this.todos.filter(function (item) {
          return !item.complete;
        }).length;
      };
      // each todo item contains a ( X ) button to delete it
      // we simply splice it from the Array using the $index
      this.deleteItem = function (index) {
        this.todos.splice(index, 1);
      };
      // the submit event for the <form> allows us to type and
      // press enter instead of ng-click on the <button> element
      // we capture $event and prevent default to prevent form submission
      // and if the label has a length, we'll unshift it into the this.todos
      // Array which will then add the new todo item into the list
      // we'll then set this.label back to an empty String
      this.onSubmit = function (event) {
        if (this.label.length) {
          this.todos.unshift({
            label: this.label,
            complete: false
          });
          this.label = '';
        }
        event.preventDefault();
      };
    },
    // instantiate the Controller as "vm" to namespace the 
    // Class-like Object
    controllerAs: 'vm',
    // our HTML template
    templateUrl: '../partials/todo.html'
  };
}

angular
  .module('Todo', [])
  .directive('todo', todo);

// manually bootstrap the application when DOMContentLoaded fires
document.addEventListener('DOMContentLoaded', function () {
  angular.bootstrap(document, ['Todo']);
});

The todo.html contents, a simple template that holds the UI logic to repeat our todo items, manage all submit/deleting functionality. This should all look pretty familiar.

<div class="todo">
  <form ng-submit="vm.onSubmit($event);">
    <h3>Todo List: ({{ vm.updateIncomplete() }} of {{ vm.todos.length }})</h3>
    <div class="todo__fields">
      <input ng-model="vm.label" class="todo__input">
      <button type="submit" class="todo__submit">
        Add <i class="fa fa-check-circle"></i>
      </button>
    </div>
  </form>
  <ul class="todo__list">
    <li ng-repeat="item in vm.todos" ng-class="{
      'todo__list--complete': item.complete
    }">
      <input type="checkbox" ng-model="item.complete">
      <p>{{ item.label }}</p>
      <span ng-click="vm.deleteItem($index);">
        <i class="fa fa-times-circle"></i>
      </span>
    </li>
  </ul>
</div>




The app is complete below:

Migration preparation

One of the design patterns I highly recommend is using the controllerAs syntax (see my article here on it) inside the Directive definition, this allows our Controllers to be free of injecting $scope and adopt a more “Class-like” way of writing Controllers. We use the this keyword to create public methods which then gets bound to the $scopeautomatically by Angular at runtime.

Using controllerAs, IMO, is a crucial step to preparing Angular 1.x components for migration to Angular 2, as the way we write components in Angular 2 uses the this keyword on an Object definition for our public methods.

Project setup/bootstrapping

Files to include, and boostrapping the application.

Angular 1.x

We’re going to walk through every single part of the setup of Angular 1.x versus Angular 2, from bootstrapping the application to creating the component, so follow closely.

We have the basic HTML page, including version 1.4.7 of Angular, and manually bootstrapping the application usingangular.bootstrap.

<!doctype html>
<html>
  <head>
    <script src="//code.angularjs.org/1.4.7/angular.min.js"></script>
  </head>
  <body>
    <todo></todo>
    <script>
      document.addEventListener('DOMContentLoaded', function () {
        angular.bootstrap(document, ['Todo']);
      });
    </script>
  </body>
</html>
Angular 2

We’re going to actually create the Angular 2 application component in ES5, there will be no ES6 and TypeScript because this will let you write Angular 2 in the browser with ease, and also the final working example is using ES5 running in JSFiddle.

There will, however, be the TypeScript/ES6 example at the end to demonstrate the full migration from 1.x to ES5, then the final ES6 + TypeScript solution.

First we need to include Angular 2, I’m not going to npm install or mess about installing dependencies, how-to steps are on the angular.io website. Let’s get up and running and learn the framework basics and migrate our Angular 1.x app.

First, we need to include Angular 2 in the <head>, you’ll notice I’m using angular2.sfx.dev.js from version 2.0.0-alpha.44. This .sfx. means it’s the self-executing bundled version, targeted at ES5 use without System loader polyfills, so we don’t need to add System.js to our project.

<!doctype html>
<html>
  <head>
    <script src="//code.angularjs.org/2.0.0-alpha.44/angular2.sfx.dev.js"></script>
  </head>
  <body>
    <todo></todo>
    <script>
      document.addEventListener('DOMContentLoaded', function () {
        ng.bootstrap(Todo);
      });
    </script>
  </body>
</html>

So far everything is super simple, instead of window.angular we have window.ng as the global namespace.

Component definition

Upgrading the Directive to an Angular 2 component.

Angular 1.x

Stripping out all the JavaScript Controller logic from the Directive leaves us with something like this:

function todo() {
  return {
    scope: {},
    controller: function () {},
    controllerAs: 'vm',
    templateUrl: '../partials/todo.html'
  };
}

angular
  .module('Todo', [])
  .directive('todo', todo);
Angular 2

In Angular 2, we create a Todo variable, which assigns the result of ng to it with corresponding chained definitions (Component, Class) – these are all new in Angular 2.

Inside .Component(), we tell Angular to use the selector: 'todo', which is exactly the same as .directive('todo', todo); in Angular 1.x. We also tell Angular where to find our template, just like in Angular 1.x we use the templateUrlproperty.

Finally, the .Class() method is what holds the logic for our component, we kick things off with a constructorproperty that acts as the “constructor” class. So far so good!

var Todo = ng
.Component({
  selector: 'todo',
  templateUrl: '../partials/todo.html'
})
.Class({
  constructor: function () {}
});

document.addEventListener('DOMContentLoaded', function () {
  ng.bootstrap(Todo);
});

Component logic

Next, it makes sense to move our Controller logic from Angular 1.x across to Angular 2’s .Class() method. If you’ve used ReactJS, this will look familiar. This is also why I suggest using controllerAs syntax because this process will be extremely simple to do.

Angular 1.x

Let’s look what we have in our todo component already. Public methods use this to bind to the $scope Object automatically for us, and we’re using controllerAs: 'vm' to namespace the instance of the Controller for use in the DOM.

controller: function () {
  this.label = '';
  this.todos = [{
    label: 'Learn Angular',
    complete: false
  },{
    label: 'Deploy to S3',
    complete: true
  },{
    label: 'Rewrite Todo Component',
    complete: true
  }];
  this.updateIncomplete = function () {
    return this.todos.filter(function (item) {
      return !item.complete;
    }).length;
  };
  this.deleteItem = function (index) {
    this.todos.splice(index, 1);
  };
  this.onSubmit = function (event) {
    if (this.label.length) {
      this.todos.unshift({
        label: this.label,
        complete: false
      });
      this.label = '';
    }
    event.preventDefault();
  };
},
controllerAs: 'vm',
Angular 2

Now, let’s kill the Controller entirely, and move these public methods into the .Class() definition inside Angular 2:

.Class({
  constructor: function () {
    this.label = '';
    this.todos = [{
      label: 'Learn Angular',
      complete: false
    },{
      label: 'Deploy to S3',
      complete: true
    },{
      label: 'Rewrite Todo Component',
      complete: true
    }];
  },
  updateIncomplete: function () {
    return this.todos.filter(function (item) {
      return !item.complete;
    }).length;
  },
  deleteItem: function (index) {
    this.todos.splice(index, 1);
  },
  onSubmit: function (event) {
    if (this.label.length) {
      this.todos.unshift({
        label: this.label,
        complete: false
      });
      this.label = '';
    }
    event.preventDefault();
  }
});

Learnings here: “public” methods become properties of the Object passed into the .Class() method, and we don’t need to refactor any of the code because in Angular 1.x we were using the controllerAs syntax alongside the thiskeyword – seamless and easy.

At this stage, the component will work, however the template we have is completely based off Angular 1.x Directives, so we need to update this.

Template migration

Here’s the entire template that we need to migrate to the new syntax:

<div class="todo">
  <form ng-submit="vm.onSubmit($event);">
    <h3>Todo List: ({{ vm.updateIncomplete() }} of {{ vm.todos.length }})</h3>
    <div class="todo__fields">
      <input ng-model="vm.label" class="todo__input">
      <button type="submit" class="todo__submit">
        Add <i class="fa fa-check-circle"></i>
      </button>
    </div>
  </form>
  <ul class="todo__list">
    <li ng-repeat="item in vm.todos" ng-class="{
      'todo__list--complete': item.complete
    }">
      <input type="checkbox" ng-model="item.complete">
      <p>{{ item.label }}</p>
      <span ng-click="vm.deleteItem($index);">
        <i class="fa fa-times-circle"></i>
      </span>
    </li>
  </ul>
</div>

Let’s be smart and attack this in chunks though, keeping only the functional pieces we need. Starting with the <form>:

<!-- Angular 1.x -->
<form ng-submit="vm.onSubmit($event);">

</form>

<!-- Angular 2 -->
<form (submit)="onSubmit($event);">
  
</form>

Key changes here are the new (submit) syntax, this indicates that an event is to be bound, where we pass in $eventas usual. Secondly, we are no longer needing a Controller, which means controllerAs is dead – note how the vm.prefix is dropped – this is fantastic.

Next up is the two-way binding on the <input>:

<!-- Angular 1.x -->
<input ng-model="vm.label" class="todo__input">

<!-- Angular 2 -->
<input [(ng-model)]="label" class="todo__input">

This sets up two-way binding on ng-model, also dropping the vm. prefix. This fully refactored section of code will look like so:

<form (submit)="onSubmit($event);">
  <h3>Todo List: ({{ updateIncomplete() }} of {{ todos.length }})</h3>
  <div class="todo__fields">
    <input [(ng-model)]="label" class="todo__input">
    <button type="submit" class="todo__submit">
      Add <i class="fa fa-check-circle"></i>
    </button>
  </div>
</form>

Moving onto the list of todo items. There’s quite a lot going on here, the ng-repeat over the todo items, a conditionalng-class to show items completed (crossed out), a checkbox to mark things as complete, and finally the ng-clickbinding to delete that specific todo item from the list.

<!-- Angular 1.x -->
<ul class="todo__list">
  <li ng-repeat="item in vm.todos" ng-class="{
    'todo__list--complete': item.complete
  }">
    <input type="checkbox" ng-model="item.complete">
    <p>{{ item.label }}</p>
    <span ng-click="vm.deleteItem($index);">
      <i class="fa fa-times-circle"></i>
    </span>
  </li>
</ul>

<!-- Angular 2 -->
<ul class="todo__list">
  <li *ng-for="#item of todos; #i = index" [ng-class]="{
    'todo__list--complete': item.complete
  }">
    <input type="checkbox" [(ng-model)]="item.complete">
    <p>{{ item.label }}</p>
    <span (click)="deleteItem(i);">
      <i class="fa fa-times-circle"></i>
    </span>
  </li>
</ul>

The differences here are mainly in the ng-repeat syntax and moving across to ng-for, which uses #item of Arraysyntax. Interestingly enough, $index isn’t given to us “for free” anymore, we have to request it and assign it to a variable to gain access to it (#i = $index) which then allows us to pass that specific Array index into the deleteItemmethod.

Altogether we have our finished Angular 2 component markup migration:

<div class="todo">
  <form (submit)="onSubmit($event);">
    <h3>Todo List: ({{ updateIncomplete() }} of {{ todos.length }})</h3>
    <div class="todo__fields">
      <input [(ng-model)]="label" class="todo__input">
      <button type="submit" class="todo__submit">
        Add <i class="fa fa-check-circle"></i>
      </button>
    </div>
  </form>
  <ul class="todo__list">
    <li *ng-for="#item of todos; #i = index" [ng-class]="{
      'todo__list--complete': item.complete
    }">
      <input type="checkbox" [(ng-model)]="item.complete">
      <p>{{ item.label }}</p>
      <span (click)="deleteItem(i);">
        <i class="fa fa-times-circle"></i>
      </span>
    </li>
  </ul>
</div>

Altogether our Angular 2 component will look like so:

var Todo = ng
.Component({
  selector: 'todo',
  template: [
    '<div class="todo">',
      '<form (submit)="onSubmit($event);">',
        '<h3>Todo List: ({{ updateIncomplete() }} of {{ todos.length }})</h3>',
        '<div class="todo__fields">',
          '<input [(ng-model)]="label" class="todo__input">',
          '<button type="submit" class="todo__submit">',
            'Add <i class="fa fa-check-circle"></i>',
          '</button>',
        '</div>',
      '</form>',
        '<ul class="todo__list">',
        '<li *ng-for="#item of todos; #i = index" [ng-class]="{',
          '\'todo__list--complete\': item.complete',
        '}">',
          '<input type="checkbox" [(ng-model)]="item.complete">',
          '<p>{{ item.label }}</p>',
          '<span (click)="deleteItem(i);">',
            '<i class="fa fa-times-circle"></i>',
          '</span>',
        '</li>',
      '</ul>',
    '</div>'
  ].join(''),
  directives: [
    ng.CORE_DIRECTIVES,
    ng.FORM_DIRECTIVES
  ]
})
.Class({
  constructor: function () {
    this.label = '';
    this.todos = [{
      label: 'Learn Angular',
      complete: false
    },{
      label: 'Deploy to S3',
      complete: true
    },{
      label: 'Rewrite Todo Component',
      complete: true
    }];
  },
  updateIncomplete: function () {
    return this.todos.filter(function (item) {
      return !item.complete;
    }).length;
  },
  deleteItem: function (index) {
    this.todos.splice(index, 1);
  },
  onSubmit: function (event) {
    if (this.label.length) {
      this.todos.unshift({
        label: this.label,
        complete: false
      });
      this.label = '';
    }
    event.preventDefault();
  }
});

It’s important to note an additional directives: [] property inside the .Component() method, this tells the component what Directives to include for us to use. We have used ng-for and ng-model which are from the COREand FORM Directive modules, so we need to explicitly define them inside the Array as dependencies:

directives: [
  ng.CORE_DIRECTIVES,
  ng.FORM_DIRECTIVES
]

And that’s it! The working solution:

Check out the Angular 2 cheatsheet, this is extremely handy when refactoring your templates from Angular 1.x to 2.

ES6 + TypeScript version

import {
  Component,
  CORE_DIRECTIVES,
  FORM_DIRECTIVES
} from 'angular2/angular2';

@Component({
  selector: 'todo'
  templateUrl: '../partials/todo.html',
  directives: [
    CORE_DIRECTIVES,
    FORM_DIRECTIVES
  ]
})

export class Todo {

  constructor() {
    this.label = '';
    this.todos = [{
      label: 'Learn Angular',
      complete: false
    },{
      label: 'Deploy to S3',
      complete: true
    },{
      label: 'Rewrite Todo Component',
      complete: true
    }];
  }

  updateIncomplete() {
    return this.todos.filter(item => !item.complete).length;
  }

  deleteItem(index) {
    this.todos.splice(index, 1);
  }

  onSubmit(event) {
    if (this.label.length) {
      this.todos.unshift({
        label: this.label,
        complete: false
      });
      this.label = '';
    }
    event.preventDefault();
  }

}

Note how we’re using ES6 import, with TypeScript @ decorators (@Component), as well as the ES6 class syntax to define a new Class to be exported.

We’re also not using any browser globals (window.ng) which is fantastic, all dependencies we need get imported from'angular2/angular2', even our directives: [] dependency Array.

Visit angular.io for everything else.

Steps to take now to prepare for Angular 2

  • Convert your application to ES6 + TypeScript
  • Refactor any Directives using a decoupled component approach
  • Refactor any Controllers to use controllerAs syntax

 

By toddmotto

Share This:

2 thoughts on “Walkthrough to upgrade an Angular 1.x component to Angular 2

  • November 25, 2015 at 9:38 pm
    Permalink

    Fantastic goods from you, man. I’ve have in mind your stuff previous to and you are simply extremely excellent. I really like what you have got here, really like what you’re saying and the best way by which you say it. You’re making it entertaining and you still care for to stay it wise. I can not wait to learn much more from you. This is actually a wonderful website.|

    Reply
  • December 15, 2015 at 1:23 am
    Permalink

    I am really inspired along wigh ylur writing abilities and also with the structure foor your
    blog. Is this a paid subject or did you modxify it yourr self?
    Anyway stay up the nice quality writing, it is rare to look a grewt weblog like this
    one today..

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *