View
395
Download
0
Category
Preview:
Citation preview
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 Demogithub.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.htmlBefore
<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
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(); }; } };});
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();};
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()
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); });};
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); });};
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' });}]);
app.jsvar 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
imageEditorModalfunction ImageEditorModalComponent() { }
ImageEditorModalComponent.prototype.close = function() { /*...*/ };
ImageEditorModalComponent.prototype.submit = function() { /*...*/ };
imageEditorModalclass ImageEditorModalComponent { close() { /*...*/ }
submit() { /*...*/ }}
.service()
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 };});
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);
Why use .service()?
● Works well with ES6 Classes● Removes another redundant concept: .
factory()● Angular 2 services are just ES6 classes
SystemJS
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});
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};
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.tsimport {UpgradeAdapter} from 'angular2/upgrade';
export let adapter = new UpgradeAdapter();
app.tsimport {adapter} from "../../adapter";
//...
adapter.bootstrap(document.documentElement, ['imageShare']);
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'));});
Replace $http with Http
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));
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()); }}
Calling getImages()api.getImages().subscribe((images) => { //Do something with images});
Migrate ImageList 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}
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); }); };}
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.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});
Migrate Modal 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};
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(); };}
Migrate ApiService 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()); }}
App.tsangular.module('imageShare', ['ngRoute']) .service('api', ApiService) .factory('http', adapter.downgradeNg2Provider(Http))
adapter.addProvider(ApiService);
Remove AngularJs 1
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]);
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 McDiarmidSlides: tinyurl.com/ng1-to-ng2
@robianmcd
Recommended