angular 2 migration - jhipster meetup 6
TRANSCRIPT
JHipster 4.0The Angular Migration
by Flavien Cathala and William Marques
Who are we ?William Marques Flavien Cathala
JHipster MemberIppon Consultant
@[email protected] 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()">×</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 ?