AngularJS 2.0 Migration Guide : codeproject.com
2016/08/31
Preparation
- Follow the John Papa’s Styleguide for Angular 1 development
- Update to the latest version of Angular 1
- All new development using components
- Switch controllers to components (component directives)
- Remove incompatible features (specific to Angular 2) from directives
- Implement manual bootstrapping (No more ng-app)
Upgrade
- Add TypeScript transpilation and build
- Start using ES6 or Javascript 2015
- Switch controllers and services to ES6 Classes
- Add Angular 2.0 to your project
- Migrate one piece at a time
Préparer la migration de AngularJS 1.x vers AngularJS 2.0 : maxlab.fr
Eviter l’usage de
$scope
et utiliser la syntaxeControllerAs
.Supprimer le
$scope
, c’est supprimer$watch
$apply
$timeout
.. des notions qui compliquent l’apprentissage du framework et sont souvent à l’origine de hacks.Au lieu de créer un controleur et d’utiliser
ng-controller
, préférez l’usage d’une directive.Tirer profit des fonctionnalités d’ECMAscript 6/2015
- Les modules : pour appliquer le paragraphe précédent sur le découplage
- Les arrow functions : pour simplifier les retours issues de promises, l’utilisation des fonctions sur les tableaux et le binding du
this
- La notation objet simplifiée et la décomposition : pour réduire le code et augmenter la lisibilité
- Les classes : pour coller à la syntaxe d’Angular 2 et éventuellement utiliser les décorateurs d’ES7
Services : Dans Angular 2, les services sont de simples classes, il est préférable de n’utiliser que
angular.service
dans Angular 1
Preparing your Angular 1 codebase to upgrade to React or Angular 2 - www.inrhythm.com - 20170424
Step 1: Integrate Webpack into your build process
- use index.js files in each dir/subdir to build a tree of files (all files : js, css and html)
- drop gulp in favor of Webpack
Step 2: Embrace ES6 modules, upgrade Angular services and controllers to ES6 classes
- replace global objects through IIFE with ES6 imports
- refactor old functions definitions for components and services with classes
Step 3: Abstract away the Angular module system and decouple your source code from the framework
- replace angular builtin helpers functions with lodash functions
- use the ui-router pattern (a single ngModule for all the app, and use the regular ES6 modules)
RETEX Step 1
Fonctionne très bien mais pas facile :
import
dans les fichiers index.js
, placer les déclarations de modules en premier dans chaque fichierjQuery
sur l'objet window
, il convient donc de l'ajouter : // webpack conf
plugins: [
// ...
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
})
]
WARNING: Tried to load angular more than once.
. Ca augmente la taille du bundle mais comme c'est temporaire on peut passer outre le temps de tout passer en modules ES6.window
(cf https://stackoverflow.com/questions/37656592/define-global-variable-with-webpack). Attention à l'ordre des imports comme pour les modules AngularJS.import Fuse from '../../../../node_modules/fuse.js/dist/fuse.min';
à ajouter en tête de fichier devant l'IIFE qui n'est pas touchée.index.js
mais direct via les composants en faisant un require
du fichier html sur la props template
plutôt que via templateUrl
./assets/css/app.css
et que dans cette css j'ai une instruction url()
qui pointe sur /assets/fonts/toto.woff
par exemple, lorsque le call sera effectué dans le browser ça ira chercher sur /assets/css/assets/fonts/toto.woff
. Il convient donc de placer les css à la racine du serveur web pour éviter ce problème.RETEX Step 2
Dans les grandes lignes :
import
/export
ES6concrètement :
blablaService
en blabla
, exporter en nommant le type de composant (Component, Filter, Service, Directive).Par exemple pour un composant référencé 'headerView'
dans l'injecteur ayant un contrôleur HeaderViewController
:
Avant on a
// header-view.component.js
angular
.module('app.header')
.component('headerView', {
template: require('./header-view.html'),
controller: HeaderViewController
});
Après on a :
// header-view.component.js
export const HeaderViewComponent = {
template: require('./header-view.html'),
controller: HeaderViewController
};
// puis la déclaration des injections de HeaderViewController
HeaderViewController.$inject = [/* ... */];
// puis l'implémentation du ctrl
function HeaderViewController(/* ... */) {
var ctrl = this;
// ...
Dans le fichier *.module.js
correspondant c'est là qu'on va désormais déclarer les composants à l'injecteur.
// header.module.js
import angular from 'angular';
// ...
import {HeaderViewComponent} from './header-view.component';
const header = angular.module('app.header', []);
// ...
header.component('headerView', HeaderViewComponent);
// on exporte le module pour l'importer à son tour dans le module de niveau supérieur
export const HeaderModule = header;
// Attention le module qui importe ce module doit set le nom en dépendant et pas le module lui même :
// app.module.js
import angular from 'angular';
import {HeaderModule} from './layout/header/header.module';
//...
const app = angular.module('app', [HeaderModule.name]);
index.js
dont les import
font doublonsindex.js
pour les dépendances et le point d'entrée de l'appli "parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
}
composants :
La fonction déclarant le controller devient une classe. On ajoute un constructeur à cette classe pour réaliser les injections de dépendances :
constructor(logger, headerMenuConfigObjectService, headerService) {
this.logger = logger;
this.headerMenuConfigObjectService = headerMenuConfigObjectService;
this.headerService = headerService;
}
On binde les services à l'instance. Le var ctrl = this;
disparait.
Les fonctions implémentants les events hooks :
ctrl.$onInit = function init() {
logger.info('app.header.HeaderViewController.$onInit()', 'start');
ctrl.grandMenu = headerMenuConfigObjectService.getMenu();
ctrl.stats = headerService.data;
ctrl.clickRefreshData = headerService.clickRefreshData;
logger.info('app.header.HeaderViewController.$onInit() ctrl.grandMenu = ', ctrl.grandMenu);
};
deviennent des fonctions de la classe :
$onInit() {
this.logger.info('app.header.HeaderView.$onInit()', 'start');
//this.grandMenu = this.headerMenuConfigObjectService.getMenu(); // inutile puisque headerMenuConfigObjectService est bind au moment de son injection
this.stats = this.headerService.data;
//this.clickRefreshData = this.headerService.clickRefreshData; // inutile puisque headerService est bind au moment de son injection
this.logger.info('app.header.HeaderView.$onInit() ctrl.grandMenu = ', this.grandMenu);
}
en l'absence de ngAnnotate, la déclaration des dépendances à injecter doit être conservée :
HeaderView.$inject = [
'logger', 'headerMenuConfigObjectService', 'headerService'
];
On se retrouve avec 3 blocs :
Attention la classe doit être impérativement déclarée en premier. Il faut donc déplacer les blocs pour avoir la classe en premier, ensuite les injections et enfin l'export.
Attention il faut transformer les fonctions anonymes des résolutions de promesses dans les composants par des arrows functions (à cause du this
)
Attention avec les services, lorsqu'on a défini l'implémentation de higher order function (reduce
, map
, filter
) dans une fonction du service/directive et qu'on la passait telle quelle (la définition de la fonction sans l'exécuter), maintenant que ces fonctions sont des méthodes de fonctions il est nécessaire d'avoir une exécution.
On ne peut plus faire :
selectedTagList.map(this.mapTagListToTagListName);
où mapTagListToTagListName
serait une méthode de la même classe telle que :
mapTagListToTagListName(item) {
return item.text;
}
on doit exécuter la méthode :
selectedTagList.map(this.mapTagListToTagListName())
et donc retourner la fonction implémentant la map
mapTagListToTagListName() {
return (item) => {
return item.text;
}
}
Très détaillé avec contextualisation + plein de bons liens en fin d'article
montre un exemple possible d'état intermédiaire (ES2015 + Babel + Webpack) lors de la migration quand on part d'un AngularJS legacy (par ex ng1.4 + ES5 + grunt/gulp).
Gillespie59/angular2-migration-sample : github.com
a short example
ngmigrate/ngmigrate.github.io : github.com
source code of the Todd Motto migration guide ng-migrate
ngParty/ng1-migration-workshop : github.com
migration example app from angular 1.x ES5 to Typescript and ngMetadata
Upgrading Your Application to Angular 2 with ng-upgrade : blog.rangle.io
Upgrading Angular apps using ngUpgrade - Pascal Precht - 20161218
NgUpgrade in Depth - Victor Savkin - 20170512
$watch
$onChanges
des composantsL'idée avec les life cycles event c'est de remplacer le dirty checking d'origine placé sur tout l'arbre des scopes par un cycle de vie centré composant par composant.
To upgrade ui-router 0.4.x to 1.0.x, we need to use a bundler and modules.
Check full example at sample-app-angularjs - github.com/ui-router
If you don't want to migrate from IIFE to ES6 modules and a bundlers (you want to upgrade ui-router first before the bundlers and ES6 modules), you can import a mono-bundle backward compatible, cf Uirouter For Angularjs Umd Bundles post.
Using <script src="../node_modules/@uirouter/angularjs/release/angular-ui-router.min.js"></script>
works well in an ES5 / AngularJS 1.6+ code base with @uirouter/[email protected]
.
The main breaking change is the removal of $rootScope
events replaced by $transitions
services hooks.
The $stateParams
service becomes deprecated. The best way to deal with it is explained here :
Inject $uiRouterGlobals
and use its attribute params [Object]
like this: $uiRouterGlobals.params.myParam