BE READY FOR ANGULAR 2.0 TODAY

Yaniv Efraim

Wix.com

About Myself

I am a Javascript engineer @Wix

Passionate about Javascript and Angular. Hacking on migrating to Angular 2.0 stuff on the last few months.

 

 

What Is Wrong With Angular 1.X?

  • Digest loop/dirty checking
  • Scope inheritance
  • Modules are not real modules
  • Directive's api is too complex
  • jQuery/jqLight

Why Is Angular 2 Better?

Angular 2 Is Going To Be Awesome!

  • Modular - es2015 modules
  • Speed & Performance
  • Support for Web Components
  • Shadow DOM
  • Can run outside of the browser
  • Less opinionated framework

What About A Migration Path?

Migration Path, Option A

(Write everything from scratch)

Migration Path, Option B

  1. Prepare current code
  2. ngUpgrade - Progressively upgrade components

What can I do in order to be ready for Angular 2.0 TODAY?

  1. Move towards component architecture
  2. Use component tree structure, with unidirectional data-flow
  3. Write Angular 1.x components using ES2015 / Typescript

01

Component Architecture

What Is A Component

An atomic piece of UI that is composable and reusable.

An Angular 2.0 Component


import {Component, View, EventEmitter} from 'angular2/core';
@Component({
    selector: 'font-size-component',
    inputs: ['fontSize'],
    outputs: ['fontSizeChange']
})
@View({
    template: ``,
    directives: [FORM_DIRECTIVES]
})
export class FontSizeComponent {
    fontSize: string;
    fontSizeChange: EventEmitter = new EventEmitter();

    modelChanged($event) {
      this.fontSizeChange.emit($event);
    }
}
					
					
<-- Interface
<-- View
<-- Logic

What Do We Want From Our Component?

  • Well-defined public API - inputs and outputs
  • Immutable - does not change data which it doesn't own
  • Isolated - no side effects / external references

Why Is It Better?

  • No more scope soup / mutable shared state
  • Component's state is well known

Let's Take This Piece Of HTML


Items

  • {{item.text}}

And Its Controller


function MainController() {
  var ctrl = this;
  ctrl.items = [{title: 'title 1', text: 'item 1'}, {...}];
  ctrl.deleteItem = function(item) {
    var idx = ctrl.items.indexOf(item);
    if (idx >= 0) {
      ctrl.items.splice(idx, 1);
    }
  };
}
					

And Create an 'item-list' Component


Items

item-list Component's Directive


module.directive('itemList', function() {
  return {
    scope: {},
    controller: function() {
      var ctrl = this;
      ctrl.deleteItem = function(item) {
        var idx = ctrl.items.indexOf(item);
        if (idx >= 0) {
          ctrl.items.splice(idx, 1);
        }
      };
    },
    controllerAs: 'itemListCtrl',
    bindToController: {
      items: '='
    },
    templateUrl: 'item-list.html'
  };
});
					

Cool! But Wait - What Is Wrong Here?

(our component is mutating data it doesn't own!)


ctrl.deleteItem = function(item) {
  var idx = ctrl.items.indexOf(item);
  if (idx >= 0) {
    ctrl.items.splice(idx, 1);
  }
};
					

Adding An Output Event


module.directive('itemList', function() {
  return {
    scope: {},
    controller: function() {
      var ctrl = this;
      ctrl.deleteItem = function(item) {
        ctrl.onDelete({item: item});
      };
    },
    controllerAs: 'itemListCtrl',
    bindToController: {
      items: '=',
      onDelete: '&'
    },
    templateUrl: 'item-list.html'
  };
});
					

Registering An Output Event


Items

We Now Have A Well Defined Component


module.directive('itemList', function() {
  return {
    scope: {},
    controller: function() {
      var ctrl = this;
      ctrl.deleteItem = function(item) {
        ctrl.onDelete({item: item});
      };
    },
    controllerAs: 'itemListCtrl',
    bindToController: {
      items: '=', //input
      onDelete: '&' //output
    },
    templateUrl: 'item-list.html'
  };
});
					
<-- Logic
<-- Interface
<-- View

Recap - item-list Component

  • Our component now has a well defined API
  • No shared mutable state
  • No side effects / external references

02

Component Tree Structure

First things first...

Component Tree Structure !== Directory Tree Structure!!

Component Tree Architecture

Angular 2.0 Component Tree Structure

Why Is It Better

  • Predictable - data flow is more explicit
  • Better control over application state
  • Scalable

React - the good parts

(example - "Thinking in React")

https://facebook.github.io/react/docs/thinking-in-react.html

Lets Do It Ourselves

https://github.com/yanivefraim/angular-theme-creator-demo/tree/angular1

"Regular" Angular 1.x (Demo - Theme Creator)


Lorem ipsum dolor sit amet, consectetur ...

Step 1 - Break the Code into Components

Step 2 - Create a Hierarchy Tree

https://github.com/yanivefraim/angular-theme-creator-demo/tree/angular1-2

Main Application Component



Theme Editor Component


Font Size Component


  directive('fontSizeComponent', function() {
 return {
  template: '',
  scope: {},
  bindToController: {
   fontSize: "@",
   fontSizeChanged: "&"
  },
  controllerAs: 'ctrl',
  controller: function() {
   var that = this;
   this.modelChanged = function() {
    that.fontSizeChanged({fontSize: that.fontSize});
   };
  }
 };
});

	

Recap - Our Application Is Now

  1. Predictable - data flows in a single direction from parent to child
  2. We have better control over application state
  3. Scalable
  4. Easier to debug

03

Write Angular 1.x Components Using ES2015/Typescript

Why ES2015?

  • Modules
  • Arrow functions
  • Block-level scope
  • Classes
  • Template Strings
  • Promises
  • And much more...

Why Typescript?

  • ES2015 superset (Transpiles to good old Javascript)
  • Type safe for development time
  • Angular 2.0 is written in Typescript
  • Better tooling

A Tiny TypeScript Example


//TypeScript
var bar: string;
var func: Function;
						

//Compiled to
var bar;
var func;
						

Convert our "Font Size" directive to ES2015


import angular from 'angular';
class FontSizeComponent {
  /* @ngInject */
  constructor() {
  }
  modelChanged() {
    this.fontSizeChanged({fontSize: this.fontSize});
  }
}
export default angular.module('themeCreatorFontSizeComponentModule', [])
.directive('fontSizeComponent', function() {
  return {
    template: ``,
    scope: {
      fontSize: "@",
      fontSizeChanged: "&"
    },
    bindToController: true,
    controllerAs: 'ctrl',
    controller: FontSizeComponent
  };
});
	
https://github.com/yanivefraim/angular-theme-creator-demo-es2015
<-- Logic
<-- View
<-- Interface

Convert our "Font Size" directive to TypeScript


import angular from 'angular';
class FontSizeComponent {
  fontSize: string; //font size is of type string
  fontSizeChanged: Function; //fontSizeChanged is of type Function
  /* @ngInject */
  constructor() {
  }
  modelChanged() {
    this.fontSizeChanged({fontSize: this.fontSize});
  }
}
export default angular.module('themeCreatorFontSizeComponentModule', [])
  .directive('fontSizeComponent', function() {
    return {
      template: ``,
      scope: {
        fontSize: "@",
        fontSizeChanged: "&"
      },
      bindToController: true,
      controllerAs: 'ctrl',
      controller: FontSizeComponent
    };
  });
	

Let's write it in Angular 2


import {Component, View, EventEmitter} from 'angular2/core';
@Component({
    selector: 'font-size-component',
    inputs: ['fontSize'],
    outputs: ['fontSizeChange']
})
@View({
    template: ``
})
export class FontSizeComponent {
    fontSize: string;
    fontSizeChange: EventEmitter = new EventEmitter();

    modelChanged($event) {
      this.fontSizeChange.emit($event);
    }
}
					
					
https://github.com/yanivefraim/angular-theme-creator-demo/tree/angular2-steps
<-- Interface
<-- View
<-- Logic

Recap - ES2015/TS

  • Our components use modern ES2015 / TypeScript
  • It is now easier to migrate to Angular 2.0 components

Bonus

Using The "New Router"

Why Building A New Router?

  • ngRoute - too simple
  • ui-router - not build for component architecture

The New Router - Component Router

  • Built with Component Structure in mind: route per component
  • Will be ported from Angular 2.0 to Angular 1.5

Porting To The New Router

  • ui-route
  • $stateProvider.state()
  • ui-view
  • ui-sref
  • ngComponentRouter
  • $router.config()
  • ng-outlet
  • ng-link

Settings - Angular 1.X


								app.controller('AppController', function ($router) {
  $router.config([
    {
      path: '/welcome',
      component: 'welcome'
    }
  ]);
});
							

Settings - Angular 1.X, Based On Conventions

  1. Load the component template asynchronously from: components/[COMPONENT_NAME]/[COMPONENT_NAME].html
  2. Instantiate:
    [COMPONENT_NAME]Controller

Summary

  • Angular 2.0 will have huge changes, but it is going to be awesome
  • Start preparing for the migration today
  • Learn
  • Try it yourself
  • Don’t be afraid of it!

Questions ?

https://github.com/yanivefraim/angular-theme-creator-demo/tree/angular1
https://github.com/yanivefraim/angular-theme-creator-demo/tree/angular1-2
https://github.com/yanivefraim/angular-theme-creator-demo-es2015
https://github.com/yanivefraim/theme-creator-demo-angular-2

Thank You