Angular 2 Migration - JHipster Meetup 6

Post on 16-Apr-2017

1,098 views 1 download

transcript

JHipster 4.0The Angular Migration

by Flavien Cathala and William Marques

Who are we ?William Marques Flavien Cathala

@wylmarqwmarques@ippon.fr

JHipster MemberIppon Consultant

@flaviencathalafcathala@ippon.frJHipster MemberEpitech Student

Summary

1.Introduction

2.Prepare your migration

3.Angular 2 ecosystem

4.Step-by-step migration

5.After the migration

Migration on JHipsterJHipster Team

VictorJulien Deepu William

Flavien Sendil Kurma

n

Migration on JHipsterContributors

Chris Thielen

UI Router founder

Sean Larkin

Webpack member

Migration on JHipsterChronology

Start of migration

June

Migration to Webpack

November

Entities

Tests

December

End ?

JanuaryDependenci

es

September

Angular 2

- New syntax- New concepts

Officially released in September 2016 with:

AngularJS vs Angular 2

Javascript TypescriptMVC Component

OrientedPromise Observable

AngularJS Angular 2

Why migrating ?

- Better performances- More adapted to mobile terminals- Better productivity- AngularJS will be deprecated

PREPARE YOURSELF

Follow the John Papa styleMajor rules :

Folder-By-Feature

Scope Isolation (controllerAs “vm”)

IIFE (avoid minification conflicts)

https://github.com/johnpapa/angular-styleguide/tree/master/a1

(that’s him)

Be modular !Create module files and declare your controllers and factories there

Code is cleaner

Easy module loader setup

Source: https://github.com/tuchk4/requirejs-angular-loader

Do ComponentsOnly available in >= 1.5.x

Easy switch to Angular 2 Component

Promote best practices (scope isolation)

One-way binding possible

angular.module('heroApp').component('heroDetail'

, {

templateUrl: 'heroDetail.html',

controller: HeroDetailController,

bindings: {

hero: '='

}

});

BONUS 1 : Do TypeScriptSpoiler : You will write TypeScript with Angular 2 (widely recommended)

Add classes, types, imports in your code

Very easy migration to Angular 2

https://codepen.io/martinmcwhorter/post/angularjs-1-x-with-typescript-or-es6-best-practices

BONUS 2 : YarnNew package manager (another one)

Much faster

Easy to use (same command as NPM)

Avoid “works on my machine” issue

Offline Installation

Angular 2 ecosystem

Module loaderWhy ?

- Code complexity

- Performances- Avoid injection of huge number of files in index

Module loader

Complexity ** **Maintainabilit

y* ***

Performances * ***

SystemJS WebpackWhich one ?

Module loaderWhy Webpack ?

Migration to Webpack

+

Webpack

webpack.common

webpack.dev

webpack.prod

polyfills

vendor

webpack.dev.js const webpackMerge = require('webpack-merge');

const commonConfig = require('./webpack.common.js');const ENV = 'prod';

module.exports = webpackMerge(commonConfig({env: ENV}), { output: { filename: '[hash].[name].bundle.js', chunkFilename: '[hash].[id].chunk.js' }, plugins: [ new ExtractTextPlugin('[hash].styles.css') ]});

Webpackwebpack.prod.js

const webpackMerge = require('webpack-merge');const commonConfig = require('./webpack.common.js');const ENV = 'dev';

module.exports = webpackMerge(commonConfig({env: ENV}), { module: { rules: [{ test: /\.ts$/, loaders: [ 'tslint' ] }] }, plugins: [ new BrowserSyncPlugin({ host: 'localhost', port: 9000, proxy: 'http://localhost:8080' }), new ExtractTextPlugin('styles.css'), new webpack.NoErrorsPlugin() ]});

polyfills.ts

import 'bootstrap/dist/css/bootstrap.min.css';import 'font-awesome/css/font-awesome.min.css';

Webpack

vendor.ts

import 'reflect-metadata/Reflect';import 'zone.js/dist/zone';

module.exports = function (options) { const DATAS = { VERSION: JSON.stringify(require("../package.json").version), DEBUG_INFO_ENABLED: options.env === 'dev' }; return { entry: { 'polyfills': './src/main/webapp/app/polyfills', 'vendor': './src/main/webapp/app/vendor', 'main': './src/main/webapp/app/app.main' }, resolve: { extensions: ['.ts', '.js'], modules: ['node_modules'] }, output: { path: './target/www', filename: '[name].bundle.js', chunkFilename: '[id].chunk.js' },

webpack.common.js

WebpackHow to run Webpack ?webpack --watch --config

webpack/webpack.dev.js

webpack -p --config webpack/webpack.prod.js

Using NPM scriptspackage.json

"scripts": { "webpack:dev": "webpack --watch --config webpack/webpack.dev.js", "webpack:prod": "webpack -p --config webpack/webpack.prod.js" }

npm run webpack:dev

npm run webpack:prod

Command lines

Package managers

THE MIGRATION

Migration Plan : Bottom Up

1.Setup migration (install Angular 2 packages, the hybrid app)

2.Migrate small controllers, services, templates (leafs) and then their parents

3.Migrate routes

4.Check 3rd party dependencies (Angular UI, Angular Translate)

5.Remove AngularJS Code / Dependencies

6.Bootstrap your Angular 2 App !

upgradeAdapter

Incremental Update (hybrid app)Upgrade your Angular 1 Services, Controllers…Downgrade your Angular 2 Services, Components… Bad performance (temporary solution)

Why ?

SetupRemove ng-app and strict-di attribute in your template

Create an app.main.ts :upgradeAdapter.bootstrap(document.body, ['myApp.app'], {strictDi: true});

Setup

import * as angular from 'angular';

import { UpgradeAdapter } from '@angular/upgrade';

import { forwardRef } from '@angular/core';

import { Ng2BasicAppModule } from './app.ng2module';

export var upgradeAdapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() =>

Ng2BasicAppModule));

Create an upgrade_adapter.ts file

UsageDowngrade an Angular 2 Component to use it in your Angular 1 App, add in your module file :

For a service :.directive(‘home’, <angular.IDirectiveFactory> upgradeAdapter.downgradeNg2Component(HomeComponent))

.factory(‘example’, adapter.downgradeNg2Provider(Example));

Controller Migration

- Remove the IIFE

- Remove the $inject

- Use the @Component annotation

- Replace controller function by class

- Remove controller declaration (angular.module…)

Controller Migrationimport { Component, OnInit, Inject } from '@angular/core';import { StateService } from "ui-router-ng2";import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';import { Account, LoginModalService, Principal } from "../shared";

@Component({ selector: 'home', templateUrl: './home.html'})export class HomeComponent implements OnInit { account: Account; modalRef: NgbModalRef;

constructor( private principal: Principal, private $state: StateService, private loginModalService: LoginModalService ) {}

ngOnInit() { this.principal.identity().then((account) => { this.account = account; }); }

isAuthenticated() { return this.principal.isAuthenticated(); }

login() { this.modalRef = this.loginModalService.open(); }}

(function() { 'use strict';

angular .module('ng2FinalApp') .controller('HomeController', HomeController);

HomeController.$inject = ['$scope', 'Principal', 'LoginService', '$state'];

function HomeController ($scope, Principal, LoginService, $state) { var vm = this;

vm.account = null; vm.isAuthenticated = null; vm.login = LoginService.open; vm.register = register; $scope.$on('authenticationSuccess', function() { getAccount(); });

getAccount();

function getAccount() { Principal.identity().then(function(account) { vm.account = account; vm.isAuthenticated = Principal.isAuthenticated; }); } function register () { $state.go('register'); } }})();

Same refactoring as controllers

Add your service in the providers array of your Angular 2 Module in order to inject it in your Angular 2 Components

Downgrade it using the upgradeAdapter in your Angular 1 Module :

Service migration

.factory('example', adapter.downgradeNg2Provider(Example));

Service Migration

1 2

(function() {

'use strict';

angular

.module('ng2FinalApp')

.factory('LogsService', LogsService);

LogsService.$inject = ['$resource'];

function LogsService ($resource) {

var service = $resource('management/jhipster/logs', {}, {

'findAll': { method: 'GET', isArray: true},

'changeLevel': { method: 'PUT'}

});

return service;

}

})();

import { Injectable } from '@angular/core';

import { Http, Response } from '@angular/http';

import { Observable } from 'rxjs/Rx';

import { Log } from './log.model';

@Injectable()

export class LogsService {

constructor(private http: Http) { }

changeLevel(log: Log): Observable<Response> {

return this.http.put('management/jhipster/logs', log);

}

findAll(): Observable<Log[]> {

return this.http.get('management/jhipster/logs').map((res:

Response) => res.json());

}

}

Angular 1 Dependency in Angular 2 ?

Upgrade the Angular 1 provider in your module file, using the upgradeAdapter:

Use the @Inject annotation in your Angular 2 controller/service constructor :

Same for Angular 1 lib dependencies ($state, $rootScope… )

@Inject('heroes') heroes: HeroesService

adapter.upgradeNg1Provider('heroes');

Routes Migration

Angular 2 Router UI-Router NG2

Default solution for Angular TeamInspired by AngularJS UI RouterComponent Oriented

Easy migration from UI Router NG 1Visualizer feature

Breaking changes from UI Router NG1 Not the official Solution

Final Decision : UI-Router NG2, because of their contribution to JHipster

NG2 Router or UI-Router ?

Route Migration: SetupInstall ui-router-ng1-to-ng2 and ui-router-ng2 NPM packages

Add uirouter.upgrade module dependency in your Angular 1 Module

Add Ng1ToNg2Module in your Angular 2 Module

let ng1module = angular.module("myApp", [uiRouter, 'ui.router.upgrade']);

@NgModule({

imports: [ BrowserModule, Ng1ToNg2Module ]

}) class SampleAppModule {}

Route Migration : UpgradeAdapterIn your upgrade_adapter.ts file, add these lines :

import { uiRouterNgUpgrade } from "ui-router-ng1-to-

ng2";

uiRouterNgUpgrade.setUpgradeAdapter(upgradeAdapter);

Route migration : refactoringimport { RegisterComponent } from './register.component';import { JhiLanguageService } from '../../shared';

export const registerState = { name: 'register', parent: 'account', url: '/register', data: { authorities: [], pageTitle: 'register.title' }, views: { 'content@': { component: RegisterComponent } }, resolve: [{ token: 'translate', deps: [JhiLanguageService], resolveFn: (languageService) => languageService.setLocations(['register']) }]};

(function() { 'use strict';

angular .module('ng2FinalApp') .config(stateConfig);

stateConfig.$inject = ['$stateProvider'];

function stateConfig($stateProvider) { $stateProvider.state('register', { parent: 'account', url: '/register', data: { authorities: [], pageTitle: 'register.title' }, views: { 'content@': { templateUrl: 'app/account/register/register.html', controller: 'RegisterController', controllerAs: 'vm' } }, resolve: { translatePartialLoader: ['$translate', '$translatePartialLoader', function ($translate, $translatePartialLoader) { $translatePartialLoader.addPart('register'); return $translate.refresh(); }] } }); }})();

Route migration : Route Declarationlet ACCOUNT_STATES = [ accountState, activateState, passwordState, finishResetState, requestResetState, registerState, sessionsState, settingsState];

@NgModule({ imports: [ Ng2BasicSharedModule, UIRouterModule.forChild({ states: ACCOUNT_STATES }) ], declarations: [ ActivateComponent, RegisterComponent, PasswordComponent, PasswordResetInitComponent, PasswordResetFinishComponent, SessionsComponent, SettingsComponent ], providers: [ SessionsService, Register, Activate, Password, PasswordResetInit, PasswordResetFinish ], schemas: [CUSTOM_ELEMENTS_SCHEMA]})export class Ng2BasicAccountModule {}

Add your routes in a variable and then import them using UIRouterModule.forChild :

Templates migration

ng-class [ngClass]ng-click (click)

ng-if *ngIfng-model [(ngModel)]ng-repeat *ngFor

AngularJS Angular 2

Templates migration<div class="modal-header"> <button class="close" type="button"

(click)="activeModal.dismiss('closed')">x </button> <h4 class="modal-title">Sign in</h4></div><div class="modal-body"> <div class="row"> <div class="col-md-4 col-md-offset-4"> <h1 jhi-translate="login.title">Sign in</h1> </div> <div class="col-md-8 col-md-offset-2"> <div class="alert-danger" *ngIf="authenticationError"> <strong>Failed to sign in!</strong> </div> </div> <div class="col-md-8 col-md-offset-2"> <form class="form" role="form" (ngSubmit)="login()"> <div class="form-group"> <label for="username">Login</label> <input type="text" class="form-control" name="username"

id="username" [(ngModel)]="username"> </div>

<div class="modal-header"> <button type="button" class="close"

ng-click="vm.cancel()">&times;</button> <h4 class="modal-title">Sign in</h4></div><div class="modal-body"> <div class="row"> <div class="col-md-4 col-md-offset-4"> <h1 data-translate="login.title">Sign in</h1> </div> <div class="col-md-8 col-md-offset-2"> <div class="alert-danger" ng-show="vm.authenticationError"> <strong>Failed to sign in!</strong> </div> </div> <div class="col-md-8 col-md-offset-2"> <form class="form" role="form" ng-submit="vm.login($event)"> <div class="form-group"> <label for="username">Login</label> <input type="text" class="form-control"

id="username" ng-model="vm.username"> </div>

Dependencies migration

- angular-ui ng-bootstrap- angular-translate ng2-translate

Some external dependencies needed to be replaced/updated:

- ...

Remove AngularJS

- Remove upgradeAdapter- Remove all AngularJS files

Once everything has been migrated:

CONCLUSION

Migration Feedback

A lot of new technologies and architecturesDifficult processSome libs are poorly documented and in alphaOnly a few projects already migratedMany different choices (Router, Module Loader)Don’t do it yourself : Use JHipster ;-)

About Angular 2...

Modern approach (Component oriented)

TypeScript Features (types, ES6)

Cleaner code

Less tools (only NPM)

What’s next ?

Router discussions

Hot Module Loader

Ahead Of Time compilation

Finish migration for all configurations

Demo

Questions ?