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
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
Preparation Overview ● $watch ● Isolate scope ● Controller as ● $parent ● .component() ● TypeScript ● .service() ● SystemJS
$watch
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
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
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
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
Image Share Demo github.com/robianmcd/angular-migration
Why avoid $watch? ● Hard to reason about ● Not declarative ● Creates unnecessary watcher ● Not in Angular 2
isolate scope
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
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
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
<image-editor-modal></image-editor-modal> imageList.html Before <image-editor-modal show="showModal" on-submit="uploadNewImage($image)"> </image-editor-modal> After
Why use isolate scope? ● Encapsulation! ● Smaller components are easier to understand ● Easier to unit test ● This is how components work in Angular 2
controller as syntax
imageEditorModal angular.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(); }; } }; });
imageEditorModal angular.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(); };
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>
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>
Why use controllers over link? ● Removes redundant concept ● Let’s you use the “controller as” syntax ● Link functions don’t exist in 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
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
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
.component()
imageList angular.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); }); };
imageList angular.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); }); };
app.js var 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' }); }]);
app.js var app = angular.module('imageShare', ['ngRoute']); app.config(['$routeProvider', function ($routeProvider) { $routeProvider .when('/images', { template: '<image-list></image-list>' }) .otherwise({ redirectTo: '/images' }); }]);
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
TypeScript
imageEditorModal function ImageEditorModalComponent() { } ImageEditorModalComponent.prototype.close = function() { /*...*/ }; ImageEditorModalComponent.prototype.submit = function() { /*...*/ };
imageEditorModal class ImageEditorModalComponent { close() { /*...*/ } submit() { /*...*/ } }
.service()
apiService.ts var 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 }; });
apiService.ts var 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);
Why use .service()? ● Works well with ES6 Classes ● Removes another redundant concept: . factory() ● Angular 2 services are just ES6 classes
SystemJS
imageEditorModal class 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 });
imageEditorModal class 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};
Why use SystemJS? ● Lets you use ES6 modules ● Angular 2 needs a module loader
Add ng-upgrade
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
adapter.ts import {UpgradeAdapter} from 'angular2/upgrade'; export let adapter = new UpgradeAdapter();
app.ts import {adapter} from "../../adapter"; //... adapter.bootstrap(document.documentElement , ['imageShare']);
gulpfile.js var 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')); });
Replace $http with Http
app.ts import {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));
apiService.ts import {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()); } }
Calling getImages() api.getImages().subscribe((images) => { //Do something with images });
Migrate ImageList to Angular 2
ImageListComponent.ts import 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}
ImageListComponent.ts import 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); }); }; }
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>
app.ts import {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});
Migrate Modal to Angular 2
imageEditorModal class 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};
imageEditorModal import {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(); }; }
Migrate ApiService to Angular 2
ApiService.ts import {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()); }
App.ts angular.module('imageShare', ['ngRoute']) .service('api', ApiService) .factory('http', adapter.downgradeNg2Provider(Http)) adapter.addProvider(ApiService);
Remove AngularJs 1
ApiService.ts import {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]);
Summary ● Angular 2 is based on components and services ● Incremental migration ● Angular 1 best practices ● Not all Angular 1 apps need to be upgraded
Rob McDiarmid Slides: tinyurl.com/ng1-to-ng2 @robianmcd

Migrating to Angular 2

  • 1.
  • 2.
    Why use Angular2 ● Takes advantage of modern web standards ○ ES6 ○ TypeScript ○ Web Components ○ Observables ○ Module Loaders ○ ZoneJS ● Removes many unnecessary concepts from Angular 1 ● Performance
  • 3.
    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
  • 4.
    Preparation Overview ● $watch ●Isolate scope ● Controller as ● $parent ● .component() ● TypeScript ● .service() ● SystemJS
  • 5.
  • 6.
    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
  • 7.
    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
  • 8.
    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
  • 9.
    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
  • 10.
  • 11.
    Why avoid $watch? ●Hard to reason about ● Not declarative ● Creates unnecessary watcher ● Not in Angular 2
  • 12.
  • 13.
    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
  • 14.
    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
  • 15.
    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
  • 16.
  • 17.
    Why use isolatescope? ● Encapsulation! ● Smaller components are easier to understand ● Easier to unit test ● This is how components work in Angular 2
  • 18.
  • 19.
    imageEditorModal angular.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(); }; } }; });
  • 20.
    imageEditorModal angular.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(); };
  • 21.
    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>
  • 22.
    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>
  • 23.
    Why use controllersover link? ● Removes redundant concept ● Let’s you use the “controller as” syntax ● Link functions don’t exist in Angular 2
  • 24.
    Why use “controlleras”? ● Don’t have to worry about scope inheritance ● Better organization ● Works well with ES6 classes ● This is how components work in Angular 2
  • 25.
    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
  • 26.
    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
  • 27.
  • 28.
    imageList angular.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); }); };
  • 29.
    imageList angular.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); }); };
  • 30.
    app.js var 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' }); }]);
  • 31.
    app.js var app =angular.module('imageShare', ['ngRoute']); app.config(['$routeProvider', function ($routeProvider) { $routeProvider .when('/images', { template: '<image-list></image-list>' }) .otherwise({ redirectTo: '/images' }); }]);
  • 32.
    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
  • 33.
  • 34.
    imageEditorModal function ImageEditorModalComponent() {} ImageEditorModalComponent.prototype.close = function() { /*...*/ }; ImageEditorModalComponent.prototype.submit = function() { /*...*/ };
  • 35.
  • 36.
  • 37.
    apiService.ts var 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 }; });
  • 38.
    apiService.ts var 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);
  • 39.
    Why use .service()? ●Works well with ES6 Classes ● Removes another redundant concept: . factory() ● Angular 2 services are just ES6 classes
  • 40.
  • 41.
    imageEditorModal class 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 });
  • 42.
    imageEditorModal class 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};
  • 43.
    Why use SystemJS? ●Lets you use ES6 modules ● Angular 2 needs a module loader
  • 44.
  • 45.
    index.html <script src="/node_modules/es6-shim/es6-shim.js"></script> <script src="/node_modules/systemjs/dist/system-polyfills.js"></script> <scriptsrc="/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
  • 46.
    adapter.ts import {UpgradeAdapter} from'angular2/upgrade'; export let adapter = new UpgradeAdapter();
  • 47.
    app.ts import {adapter} from"../../adapter"; //... adapter.bootstrap(document.documentElement , ['imageShare']);
  • 48.
    gulpfile.js var 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')); });
  • 49.
  • 52.
    app.ts import {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));
  • 53.
    apiService.ts import {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()); } }
  • 54.
  • 55.
  • 58.
    ImageListComponent.ts import 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}
  • 59.
    ImageListComponent.ts import 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); }); }; }
  • 60.
    ImageList.html <div> <div class="input-group"> <button class="btnbtn-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>
  • 61.
    app.ts import {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});
  • 62.
  • 65.
    imageEditorModal class 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};
  • 66.
    imageEditorModal import {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(); }; }
  • 67.
  • 70.
    ApiService.ts import {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()); }
  • 71.
    App.ts angular.module('imageShare', ['ngRoute']) .service('api', ApiService) .factory('http',adapter.downgradeNg2Provider(Http)) adapter.addProvider(ApiService);
  • 72.
  • 75.
    ApiService.ts import {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]);
  • 76.
    Summary ● Angular 2is based on components and services ● Incremental migration ● Angular 1 best practices ● Not all Angular 1 apps need to be upgraded
  • 77.