BE READY FOR ANGULAR 2.0 TODAY – What Do We Want From Our Component? – Recap - item-list Component



BE READY FOR ANGULAR 2.0 TODAY – What Do We Want From Our Component? – Recap - item-list Component

4 3


be-ready-for-angular2-today

This is my presentation for "Be ready for Angular 2.0 today"

On Github yanivefraim / be-ready-for-angular2-today

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

Prepare current code ngUpgrade - Progressively upgrade components

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

Move towards component architecture Use component tree structure, with unidirectional data-flow 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: `<input [ng-model]="fontSize" id="fontSize">`,
    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

<div ng-controller="MainCtrl as mainCtrl">
  <h1>Items</h1>
  <ul>
    <li ng-repeat="item in mainCtrl.items">
      {{item.text}}
      <button ng-click="mainCtrl.deleteItem(item)">
        Delete
      </button>
    </li>
  </ul>
</div>

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

<div ng-controller="MainCtrl as mainCtrl">
  <h1>Items</h1>
  <!-- <ul>
    <li ng-repeat="item in mainCtrl.items">
      { {item.text}}
      <button ng-click="mainCtrl.deleteItem(item)">
        Delete
      </button>
    </li>
  </ul> -->
  <item-list items="mainCtrl.items"></item-list>
</div>

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

<div ng-controller="MainCtrl as mainCtrl">
  <h1>Items</h1>
  <item-list items="mainCtrl.items" on-delete="mainCtrl.onDelete(item)"></item-list>
</div>

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
  • A component has a well-defined public API of property and event bindings
  • Component isolation
  • Limits the number of ways a component can be modified, makes this modification predictable
  • Component's state is well known
  • Immutable Data - better performance and predictability

React - the good parts

(example - "Thinking in React")

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

Lets Do It Ourselves

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

<div ng-controller="MainCtrl as ctrl">
  <div class="left-sidebar">
    <select class="form-control" ng-model="ctrl.fontFamily" ng-options="font for font in ctrl.fonts"></select>
    <input id="fontColor" ng-model="ctrl.fontColor" placeholder="#nnnnnn or <color-name>">
    <input ng-model="ctrl.fontSize">
  </div>
  <div class="main panel panel-primary">
    <div ng-style="{'font-family': ctrl.fontFamily, 'color': ctrl.fontColor, 'font-size': ctrl.fontSize}">
      <p>Lorem ipsum dolor sit amet, consectetur ...</p>
    </div>
  </div>
</div>

Step 1 - Break the Code into Components

Step 2 - Create a Hierarchy Tree

Main Application Component

<div class="left-sidebar">
  <theme-editor-panel-component font-size="{{ctrl.fontSize}}" font-family="{{ctrl.fontFamily}}" font-color="{{ctrl.fontColor}}" on-font-size-change="ctrl.onFontSizeChange(fontSize)" on-font-color-change="ctrl.onFontColorChange(fontColor)" on-font-family-change="ctrl.onFontFamilyChange(fontFamily)"></theme-editor-panel-component>
</div>
<div class="main panel panel-primary">
  <theme-preview-panel-component font-size="{{ctrl.fontSize}}" font-family="{{ctrl.fontFamily}}" font-color="{{ctrl.fontColor}}"></theme-preview-panel-component>
</div>

Theme Editor Component

<div class="control-group">
  <label class="control-label">Font Family</label>
  <font-family-component font-family="{{ctrl.fontFamily}}" font-family-changed="ctrl.onFontFamilyChangeEvent(fontFamily)"></font-family-component>
</div>
<div class="control-group">
  <label class="control-label" for="fontColor">Font Color</label>
  <font-color-component font-color="{{ctrl.fontColor}}" font-color-changed="ctrl.onFontColorChangeEvent(fontColor)"></font-color-component>
</div>
<div class="control-group">
  <label class="control-label" for="fontSize">Font Size</label>
  <font-size-component font-size="{{ctrl.fontSize}}" font-size-changed="ctrl.onFontSizeChangeEvent(fontSize)"></font-size-component>
</div>

Font Size Component

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

Recap - Our Application Is Now

Predictable - data flows in a single direction from parent to child We have better control over application state Scalable 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: `<input ng-model="ctrl.fontSize" ng-change="ctrl.modelChanged()">`,
    scope: {
      fontSize: "@",
      fontSizeChanged: "&"
    },
    bindToController: true,
    controllerAs: 'ctrl',
    controller: FontSizeComponent
  };
});
<-- 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: `<input ng-model="ctrl.fontSize" ng-change="ctrl.modelChanged()">`,
      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: `<input [ngmodel]="fontSize" (ngmodelchanged)="modelChanged($event)" id="fontSize">`
})
export class FontSizeComponent {
    fontSize: string;
    fontSizeChange: EventEmitter = new EventEmitter();

    modelChanged($event) {
      this.fontSizeChange.emit($event);
    }
}
					
<-- 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

Load the component template asynchronously from: components/[COMPONENT_NAME]/[COMPONENT_NAME].html 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 ?

Thank You

BE READY FOR ANGULAR 2.0 TODAY Yaniv Efraim Wix.com