We are in the process of upgrading an AngularJS application to Angular with the incremental approach: we would like to be able to create new components in Angular and upgrade existing AngularJS components one by one, all this still with a functional application during the process.
We use the official documentation as well as several articles about hybrid Angular/AngularJS applications on real world applications.
Here are our attempts and the errors we get.
Context
- AngularJS 1.7.3
- Angular 6.1.7
- Typescript 2.7.2
- angular-cli
First steps
- upgrade to AngularJS 1.7
- remove Grunt and use angular-cli
- use ngUpgrade (app.module.ts and app.module.ajs.ts)
Moving to Typscript: dealing with errors
That's the official process: rename .js files to .ts.
We then moved from Node require() to TypeScript module loading (var ... = require
--> import ... = require
)
Ideally, we should correct all the typing errors due to using the TypeScript compiler.
But the TypeScript doc states that's it's possible to do an incremental migration: being tolerant to TypeScript errors at the beginning in order to compile the js code without modifications (and stricter later on after fixing the code).
To achieve this, we used the awesome-typescript-loader instead of tsc to get theses options: transpileOnly, errorsAsWarnings (this requires the use of angular-builders/custom-webpack).
The options allow to pass the compilation, but it appears that the compilation is done twice: first without errors (warnings or not), but second with errors... so the build fails. How can we run only the first compilation?
Alternative attempt: keeping .js files, errors in importing and bootstrapping
We tried also an unofficial and unusual way to migrate the js code incrementally, that is keeping the original .js files alongside new .ts files.
We got some errors at compilation or application loading, related to importing AngularJS and to module management. Indeed the TypsScript module documentation states that:
Some libraries are designed to be used in many module loaders, or with no module loading (global variables). These are known as UMD modules. These libraries can be accessed through either an import or a global variable.
... can be used as a global variable, but only inside of a script. (A script is a file with no imports or exports.)
What we noticed:
in .js files: access to the AngularJS global angular (if we remove require("angular")) - script mode
in .ts files: we can't use import from angular, otherwise we get the error
angular.module is undefined
- module mode
With this in mind, we made the application compile and load in the browser without errors, but at the end:
this.upgrade.bootstrap(document.body, [App])
generates an error on AngularJS bootstrapping:
Angular 1.x not loaded !
How to fix this? It may be because we didn't import AngularJS in the TypeScript module way to avoid the previous error?
The official documentation mentions angular.IAngularStatic (still get an error)?
We can try also setAngularJSGlobal() ? Used when AngularJS is loaded lazily, and not available on window
By the way what is the difference between using [App] or ["App"] when calling bootstrap()?
... Since we are new to this upgrade process, we may be doing completely wrong things. Thank you for sharing your experience!
Configuration files
angular.json
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "acd-banner-multicanal",
"projects": {
"acd-banner-multicanal": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "target",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "./tsconfig.json",
"assets": [
"src/i18n",
"src/conf/conf.txt",
"src/conf/conf_DEFAULT.txt",
"src/systemjs.config.js",
{ "glob": "font-banner*", "input": "./node_modules/elq-font-icon/build/", "output": "/assets/fonts" },
"src/assets/images",
{ "glob": "system.js*", "input": "./node_modules/systemjs/dist/", "output": "/assets" },
"src/assets/images",
{ "glob": "**/*", "input": "./node_modules/tinymce/plugins", "output": "/assets/tinymce/plugins" },
{ "glob": "**/*", "input": "./node_modules/tinymce/skins", "output": "/assets/tinymce/skins" }
],
"styles": [
"src/assets/styles/style.less"
],
"scripts": [
"./node_modules/jquery/dist/jquery.js",
"./node_modules/jquery-ui-dist/jquery-ui.js"
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"aot": true,
"buildOptimizer": true
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "./karma.conf.js",
"scripts": [],
"styles": [
"src/assets/main.less"
],
"assets": [
"src/i18n",
"src/favicon.ico"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"acd-ihm-angular-e2e": {
"root": "e2e/",
"sourceRoot": "e2e",
"projectType": "application",
}
},
"defaultProject": "acd-banner-multicanal",
"schematics": {
"@schematics/angular:component": {
"styleext": "less",
"lintFix": true
}
}
}
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./target",
"sourceMap": true,
"experimentalDecorators": true,
"allowJs": true,
"baseUrl": "./",
"lib": [
"es2017",
"dom"
],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"paths": {
"angular": ["node_modules/angular/angular"]
}
},
"include": [
"src/**/*"
],
"exclude": [
"src/**/*.spec.ts"
]
}