migrating to angular 2

77
Migrating to Angular 2

Upload: fitc

Post on 20-Jan-2017

394 views

Category:

Internet


0 download

TRANSCRIPT

Page 1: Migrating to Angular 2

Migrating to Angular 2

Page 2: Migrating to Angular 2

Why use Angular 2● Takes advantage of modern web standards

○ ES6○ TypeScript○ Web Components○ Observables○ Module Loaders○ ZoneJS

● Removes many unnecessary concepts from Angular 1● Performance

Page 3: Migrating to Angular 2

What is ng-upgrade?

● Lets you run angular 1 and 2 in the same app● Use Angular 1 service in Angular 2● Use Angular 1 component in Angular 2● Use Angular 2 service in Angular 1● Use Angular 2 component in Angular 1

Page 4: Migrating to Angular 2

Preparation Overview

● $watch● Isolate scope● Controller as● $parent● .component()● TypeScript● .service()● SystemJS

Page 5: Migrating to Angular 2

$watch

Page 6: Migrating to Angular 2

Format date with: $watch<body ng-app="myApp" ng-controller="MainCtrl as ctrl"> Date: <input type="text" ng-model="ctrl.rawDate"> <div>Formatted Date: {{ ctrl.dateStr}}</div></body>

function MainCtrl($scope) { var self = this; $scope.$watch('ctrl.rawDate', function(newVal) { if (newVal !== undefined) { self.dateStr = moment(self.rawDate).format('MMM DD YYYY'); } });}

Plunker

Page 7: Migrating to Angular 2

Format date with: ng-change<body ng-app="myApp" ng-controller="MainCtrl as ctrl"> <h1>Date Formatter</ h1> Date: <input type="text" ng-model="ctrl.rawDate" ng-change="ctrl.onChange()"> <div>Formatted Date: {{ ctrl.dateStr}}</div></body>

function MainCtrl() { }

MainCtrl.prototype.onChange = function() { this.dateStr = moment(this.rawDate).format('MMM DD YYYY');};

Plunker

Page 8: Migrating to Angular 2

Format date with: getterSetter<body ng-app="myApp" ng-controller="MainCtrl as ctrl"> <h1>Date Formatter</ h1> Date: <input type="text" ng-model="ctrl.rawDate" ng-model-options= "{ getterSetter: true }"> <div>Formatted Date: {{ ctrl.dateStr}}</div></body>

function MainCtrl() { }

MainCtrl.prototype.rawDate = function(rawDate) { if (rawDate !== undefined) { this._rawDate = rawDate; this.dateStr = moment(rawDate). format('MMM DD YYYY'); } else { return this._rawDate; }};

Plunker

Page 9: Migrating to Angular 2

Format date with: function<body ng-app="myApp" ng-controller="MainCtrl as ctrl"> <h1>Date Formatter</ h1> Date: <input type="text" ng-model="ctrl.rawDate"> <div>Formatted Date: {{ ctrl.getDateStr()}}</div></body>

function MainCtrl() { }

MainCtrl.prototype.getDateStr = function() { if (this.rawDate !== undefined) { return moment(this.rawDate).format('MMM DD YYYY'); }};

Plunker

Page 10: Migrating to Angular 2

Image Share Demogithub.com/robianmcd/angular-migration

Page 11: Migrating to Angular 2

Why avoid $watch?

● Hard to reason about● Not declarative● Creates unnecessary watcher● Not in Angular 2

Page 12: Migrating to Angular 2

isolate scope

Page 13: Migrating to Angular 2

angular.module('imageShare').directive('imageEditorModal', function () { return { restrict: 'E', templateUrl: 'src/components/imageEditorModal/imageEditorModal.html', link: function(scope) { scope. close = function () { scope. showModal = false; scope. url = ''; scope. description = ''; };

scope. submit = function() { scope. uploadNewImage({/*...*/}); scope. close(); }; } };});

imageEditorModal

Page 14: Migrating to Angular 2

angular.module('imageShare').directive('imageEditorModal', function () { return { restrict: 'E', templateUrl: 'src/components/imageEditorModal/imageEditorModal.html', scope: {}, link: function(scope) { scope. close = function () { scope .$parent.showModal = false; scope. url = ''; scope. description = ''; };

scope. submit = function() { scope .$parent.uploadNewImage({ /*...*/}); scope. close(); }; } };});

imageEditorModal

Page 15: Migrating to Angular 2

angular.module('imageShare').directive('imageEditorModal', function () { return { restrict: 'E', templateUrl: 'src/components/imageEditorModal/imageEditorModal.html', scope: { show: '=', onSubmit: '&' }, link: function(scope) { scope. close = function () { scope. show = false; scope. url = ''; scope. description = ''; };

scope. submit = function() { scope. onSubmit({$image: {/*...*/}}); scope. close(); }; } };});

imageEditorModal

Page 16: Migrating to Angular 2

<image-editor-modal></image-editor-modal>

imageList.htmlBefore

<image-editor-modal show="showModal" on-submit="uploadNewImage($image)"></image-editor-modal>

After

Page 17: Migrating to Angular 2

Why use isolate scope?

● Encapsulation!● Smaller components are easier to understand● Easier to unit test● This is how components work in Angular 2

Page 18: Migrating to Angular 2

controller as syntax

Page 19: Migrating to Angular 2

imageEditorModalangular.module('imageShare').directive('imageEditorModal', function () { return { /*...*/ link: function (scope) {

scope. close = function () { scope. show = false; scope. url = ''; scope. description = ''; };

scope. submit = function () { scope. onSubmit({$image: {/*...*/}}); scope. close(); }; } };});

Page 20: Migrating to Angular 2

imageEditorModalangular.module('imageShare').directive('imageEditorModal', function () { return { /*...*/ controller: ImageEditorModalCtrl, controllerAs: '$ctrl', bindToController: true };});

function ImageEditorModalCtrl() { }

ImageEditorModalCtrl.prototype.close = function () { this.show = false; this.url = ''; this.description = '';};

ImageEditorModalCtrl.prototype.submit = function () { this.onSubmit({$image: {/*...*/}}); this.close();};

Page 21: Migrating to Angular 2

imageEditorModal.html<div ng-show="show"> <div class="modal-background" ng-click="cancel()"></div> <div class="modal-content"> <form name="form" ng-submit="submit()"> <div class="form-group"> <label for="url">Image Url</label> <input id="url" ng-model="url"> </div> <div class="form-group"> <label for="description">Description</label> <textarea id="description" ng-model="description"> </ textarea> </div> <div class="pull-right"><!--...--></div> </form> </div></div>

Page 22: Migrating to Angular 2

imageEditorModal.html<div ng-show="$ctrl.show"> <div class="modal-background" ng-click="$ctrl.cancel()"></div> <div class="modal-content"> <form name="form" ng-submit="$ctrl.submit()"> <div class="form-group"> <label for="url">Image Url</label> <input id="url" ng-model="$ctrl.url"> </div> <div class="form-group"> <label for="description">Description</label> <textarea id="description" ng-model="$ctrl.description"> </ textarea> </div> <div class="pull-right"><!--...--></div> </form> </div></div>

Page 23: Migrating to Angular 2

Why use controllers over link?

● Removes redundant concept● Let’s you use the “controller as” syntax● Link functions don’t exist in Angular 2

Page 24: Migrating to Angular 2

Why use “controller as”?

● Don’t have to worry about scope inheritance● Better organization● Works well with ES6 classes● This is how components work in Angular 2

Page 25: Migrating to Angular 2

Why use bindToController?

● Lets you use your controller for everything● Don’t need to use $scope anymore, which

isn’t in Angular 2● This is how components work in Angular 2

Page 26: Migrating to Angular 2

Why avoid $parent?

● Leads to brittle code● Breaks encapsulation● Makes unit testing hard● Requires understanding scope inheritance● It’s just the worst● Can’t use it in Angular 2

Page 27: Migrating to Angular 2

.component()

Page 28: Migrating to Angular 2

imageListangular.module('imageShare').controller('ImageListCtrl', ImageListCtrl);

function ImageListCtrl(api) { var self = this; this.api = api;

api.getImages().then(function (images) { self.images = images; });}

ImageListCtrl.prototype.addImage = function () { this.showModal = true;};

ImageListCtrl.prototype.uploadNewImage = function (image) { var self = this; this.api.createImage(image).then(function (createdImage) { self.images.unshift(createdImage); });};

Page 29: Migrating to Angular 2

imageListangular.module('imageShare').component('imageList', { templateUrl: 'src/components/imageList/imageList.html', controller: ImageListComponent});function ImageListComponent(api) { var self = this; this.api = api;

api.getImages().then(function (images) { self.images = images; });}ImageListComponent.prototype.addImage = function () { this.showModal = true;};ImageListComponent.prototype.uploadNewImage = function (image) { var self = this; this.api.createImage(image).then(function (createdImage) { self.images.unshift(createdImage); });};

Page 30: Migrating to Angular 2

app.jsvar app = angular.module('imageShare', ['ngRoute']);

app.config(['$routeProvider', function ($routeProvider) {

$routeProvider .when('/images', { templateUrl: 'src/components/imageList/imageList.html', controller: 'ImageListCtrl as $ctrl' }) .otherwise({ redirectTo: '/images' });}]);

Page 31: Migrating to Angular 2

app.jsvar app = angular.module('imageShare', ['ngRoute']);

app.config(['$routeProvider', function ($routeProvider) {

$routeProvider .when('/images', { template: '<image-list></image-list>' }) .otherwise({ redirectTo: '/images' });}]);

Page 32: Migrating to Angular 2

Why use .component()?

● Nicer syntax than .directive()● Uses “controller as” by default● Uses bindToController by default● Consolidates many redundant concepts into

components. E.g. ng-controller, .controller(), ng-include, router controllers, router views, .directive()

● Very similar to components in Angular 2

Page 33: Migrating to Angular 2

TypeScript

Page 34: Migrating to Angular 2

imageEditorModalfunction ImageEditorModalComponent() { }

ImageEditorModalComponent.prototype.close = function() { /*...*/ };

ImageEditorModalComponent.prototype.submit = function() { /*...*/ };

Page 35: Migrating to Angular 2

imageEditorModalclass ImageEditorModalComponent { close() { /*...*/ }

submit() { /*...*/ }}

Page 36: Migrating to Angular 2

.service()

Page 37: Migrating to Angular 2

apiService.tsvar IMAGES_URL = 'https://image-share.herokuapp.com/api/images';

angular.module('imageShare').factory('api', function ($http: ng.IHttpService) { function getImages() { return $http.get(IMAGES_URL).then((response) => { return response.data; }); }

function createImage(image) { return $http.post(IMAGES_URL, image).then((response) => { return response.data; }); }

return { getImages: getImages, createImage: createImage };});

Page 38: Migrating to Angular 2

apiService.tsvar IMAGES_URL = 'https://image-share.herokuapp.com/api/images';

class ApiService { constructor(private $http: ng.IHttpService) { }

getImages(): ng.IPromise<Image[]> { return this.$http.get(IMAGES_URL).then((response) => { return response.data; }); }

createImage(image): ng.IPromise<Image> { return this.$http.post(IMAGES_URL, image).then((response) => { return response.data; }); }}

angular.module('imageShare').service('api', ApiService);

Page 39: Migrating to Angular 2

Why use .service()?

● Works well with ES6 Classes● Removes another redundant concept: .

factory()● Angular 2 services are just ES6 classes

Page 40: Migrating to Angular 2

SystemJS

Page 41: Migrating to Angular 2

imageEditorModalclass ImageEditorModalComponent { show: boolean = false; url: string; description: string; onSubmit: (args: {$image: Image}) => void;

close() { /*...*/}; submit() {/*...*/};}

angular.module('imageShare').component('imageEditorModal', { templateUrl: 'src/components/imageEditorModal/imageEditorModal.html', bindings: { show: '=', onSubmit: '&' }, controller: ImageEditorModalComponent});

Page 42: Migrating to Angular 2

imageEditorModalclass ImageEditorModalComponent { show: boolean = false; url: string; description: string; onSubmit: (args: {$image: Image}) => void;

close() { /*...*/}; submit() {/*...*/};}

const imageEditorModalOptions = { templateUrl: 'src/components/imageEditorModal/imageEditorModal.html', bindings: { show: '=', onSubmit: '&' }, controller: ImageEditorModalComponent};

export {ImageEditorModalComponent, imageEditorModalOptions};

Page 43: Migrating to Angular 2

Why use SystemJS?

● Lets you use ES6 modules● Angular 2 needs a module loader

Page 44: Migrating to Angular 2

Add ng-upgrade

Page 45: Migrating to Angular 2

index.html

<script src="/node_modules/es6-shim/es6-shim.js"></script><script src="/node_modules/systemjs/dist/system-polyfills.js"></script><script src="/node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script><script src="/node_modules/angular2/bundles/angular2-polyfills.js"></script>

<script src="/node_modules/rxjs/bundles/Rx.js"></script><script src="/node_modules/angular2/bundles/angular2.dev.js"></script><script src="/node_modules/angular2/bundles/http.dev.js"></script><script src="/node_modules/angular2/bundles/upgrade.dev.js"></script>

Add the following scripts

Page 46: Migrating to Angular 2

adapter.tsimport {UpgradeAdapter} from 'angular2/upgrade';

export let adapter = new UpgradeAdapter();

Page 47: Migrating to Angular 2

app.tsimport {adapter} from "../../adapter";

//...

adapter.bootstrap(document.documentElement, ['imageShare']);

Page 48: Migrating to Angular 2

gulpfile.jsvar gulp = require('gulp');var ts = require('gulp-typescript');

gulp.task('ts', function () { return gulp.src([ 'src/**/*.ts', 'typings/**/*.ts', //Taken from https://github.com/angular/angular/issues/7280 'node_modules/angular2/typings/browser.d.ts' ]) .pipe( ts({ target: 'ES5', module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, moduleResolution: 'node' })) .pipe( gulp.dest('src'));});

Page 49: Migrating to Angular 2

Replace $http with Http

Page 50: Migrating to Angular 2
Page 51: Migrating to Angular 2
Page 52: Migrating to Angular 2

app.tsimport {adapter} from "../../adapter";import {HTTP_PROVIDERS, Http} from "angular2/http";import 'rxjs/add/operator/map';

adapter.addProvider(HTTP_PROVIDERS);

angular.module('imageShare', ['ngRoute']) .factory('http', adapter.downgradeNg2Provider(Http));

Page 53: Migrating to Angular 2

apiService.tsimport {Http, Headers} from "angular2/http";import {Observable} from "rxjs/Observable";const IMAGES_URL = 'https://image-share.herokuapp.com/api/images';

export default class ApiService { constructor(private http: Http) { }

getImages(): Observable<Image[]> { return this.http.get(IMAGES_URL) .map(res => res.json()); }

createImage(image): Observable<Image> { var headers = new Headers(); headers.append('Content-Type', 'application/json'); return this.http.post(IMAGES_URL,JSON.stringify(image), { headers: headers}) .map(res => res.json()); }}

Page 54: Migrating to Angular 2

Calling getImages()api.getImages().subscribe((images) => { //Do something with images});

Page 55: Migrating to Angular 2

Migrate ImageList to Angular 2

Page 56: Migrating to Angular 2
Page 57: Migrating to Angular 2
Page 58: Migrating to Angular 2

ImageListComponent.tsimport ApiService from "../../services/apiService";class ImageListComponent { //...

uploadNewImage(image) { this.api.createImage(image).subscribe((createdImage) => { this.images.unshift(createdImage); }); };}

const imageListOptions = { templateUrl: 'src/components/imageList/imageList.html', controller: ImageListComponent};

export {ImageListComponent, imageListOptions}

Page 59: Migrating to Angular 2

ImageListComponent.tsimport ApiService from "../../services/apiService";import {adapter} from "../../adapter";import {Component} from "angular2/core";

@Component({ templateUrl: 'src/components/imageList/imageList.html', selector: 'image-list', directives: [adapter.upgradeNg1Component( 'imageEditorModal')]})export class ImageListComponent { //... uploadNewImage(event) { this.api.createImage(event.$image).subscribe((createdImage) => { this.images.unshift(createdImage); }); };}

Page 60: Migrating to Angular 2

ImageList.html<div> <div class="input-group"> <button class="btn btn-primary" (click)="addImage()">Add Image</button> </div> <ul class="list-group"> <li *ngFor="#image of images" class="list-group-item"> <div class="media"> <div class="media-left"> < img [src]="image.url"> </ div> <div class="media-body"> {{image. description}} </ div> </div> </li> </ul></div><image-editor-modal [(show)]="showModal" (onSubmit)="uploadNewImage($event)"></image-editor-modal>

Page 61: Migrating to Angular 2

app.tsimport {adapter} from "../../adapter";import {ImageListComponent} from "../imageList/imageListComponent";import ApiService from "../../services/apiService";

angular.module('imageShare', ['ngRoute']) .directive('imageList', adapter.downgradeNg2Component(ImageListComponent));

adapter.upgradeNg1Provider( 'api', {asToken: ApiService});

Page 62: Migrating to Angular 2

Migrate Modal to Angular 2

Page 63: Migrating to Angular 2
Page 64: Migrating to Angular 2
Page 65: Migrating to Angular 2

imageEditorModalclass ImageEditorModalComponent { //... close() { this.show = false; this.url = ''; this.description = ''; };}

const imageEditorModalOptions = { templateUrl: 'src/components/imageEditorModal/imageEditorModal.html', bindings: { show: '=', onSubmit: '&' }, controller: ImageEditorModalComponent};

export {ImageEditorModalComponent, imageEditorModalOptions};

Page 66: Migrating to Angular 2

imageEditorModalimport {Component, Input, Output, EventEmitter} from "angular2/core";@Component({ templateUrl: 'src/components/imageEditorModal/imageEditorModal.html', selector: 'image-editor-modal'})export class ImageEditorModalComponent { url: string; description: string; @Input() show: boolean; @Output() showChange = new EventEmitter(); @Output() onSubmit = new EventEmitter(); close() { this.showChange.emit(false); this.url = ''; this.description = ''; }; submit() { this.onSubmit.emit({url: this.url, description: this.description}); this.close(); };}

Page 67: Migrating to Angular 2

Migrate ApiService to Angular 2

Page 68: Migrating to Angular 2
Page 69: Migrating to Angular 2
Page 70: Migrating to Angular 2

ApiService.tsimport {Injectable} from "angular2/core";const IMAGES_URL = 'https://image-share.herokuapp.com/api/images';

@Injectable()export default class ApiService { constructor(private http: Http) {

}

getImages(): Observable<Image[]> { return this.http.get(IMAGES_URL) .map(res => res.json()); }

createImage(image): Observable<Image> { var headers = new Headers(); headers.append('Content-Type', 'application/json'); return this.http.post(IMAGES_URL,JSON.stringify(image), { headers: headers}) .map(res => res.json()); }}

Page 71: Migrating to Angular 2

App.tsangular.module('imageShare', ['ngRoute']) .service('api', ApiService) .factory('http', adapter.downgradeNg2Provider(Http))

adapter.addProvider(ApiService);

Page 72: Migrating to Angular 2

Remove AngularJs 1

Page 73: Migrating to Angular 2
Page 74: Migrating to Angular 2
Page 75: Migrating to Angular 2

ApiService.tsimport {ROUTER_DIRECTIVES, RouteConfig, Route, ROUTER_PROVIDERS} from "angular2/router";import {Component} from "angular2/core";import {bootstrap} from "angular2/platform/browser";

@Component({ selector: 'app', template: '<router-outlet></router-outlet>', directives: [ROUTER_DIRECTIVES]})@RouteConfig([ new Route({ path: '/home', name: 'ImageList', component: ImageListComponent, useAsDefault: true })])class App { }

bootstrap(App, [HTTP_PROVIDERS, ROUTER_PROVIDERS, ApiService]);

Page 76: Migrating to Angular 2

Summary

● Angular 2 is based on components and services

● Incremental migration● Angular 1 best practices● Not all Angular 1 apps need to be upgraded

Page 77: Migrating to Angular 2

Rob McDiarmidSlides: tinyurl.com/ng1-to-ng2

@robianmcd