Before starting
Read the sections Angular Upgrade Tutorial / Preparation and Angular Upgrade Tutorial / Upgrading with The Upgrade Module
to get a background on how the Angular Upgrade Module works.
Setup the environment and application base
It goes without saying that NodeJs shall be installed.
Install Angular CLI globally:
$ npm install -g @angular/cli
Let's create a catalog convert-phonecat
to work in:
$ mkdir convert-phonecat
$ cd convert-phonecat
Clone the AngularJS Phonecat application:
$ git clone https://github.com/angular/angular-phonecat.git
After this, create the new hybrid application angular-phonecat-hybrid
:
$ ng new angular-phonecat-hybrid
We will now have the following structure
convert-phonecat
|
+-- angular-phonecat
| |
| +-- app
| : :
|
+-- angular-phonecat-hybrid
|
+-- src
: |
: +-- app
: : :
and you can run the applications according to their instructions in their README:s.
Copy source code
Start by copying the angular-phonecat/app
-catalogue into angular-phonecat-hybrid/src/app-ajs
resulting in:
convert-phonecat
|
+-- angular-phonecat
| |
| +-- app
| : :
|
+-- angular-phonecat-hybrid
|
+-- src
: |
: +-- app
: | :
: |
: +-- app-ajs
: : :
Now, the angular-phonecat-hybrid
application contains all source code from the old angular-phonecat
application .
The app-ajs
catalogue contains a bower_components
catalogue. Just delete it. It is not needed since we will have all our dependencies based on npm.
Remove animations
This tutorial will not cover animations so remove the following files:
- app-ajs/app.animations.js
- app-ajs/app.animations.css
Add dependencies
Add the following dependencies to the project (from now on the project is synonym to the angular-phonecat-hybrid
project). First the Angular Upgrade Module package and the AngularJS packages:
$ npm install --save \
@angular/upgrade \
[email protected] \
[email protected] \
[email protected] \
[email protected]
Then some type definitions:
$ npm install --save-dev \
@types/angular \
@types/angular-cookies \
@types/angular-mocks \
@types/angular-resource \
@types/angular-route \
@types/angular-sanitize
Convert to Typescript
First we just change the extension to .ts on all .js files in src/app-ajs
.
One easy way to do this is to install renamer:
$ npm i -g renamer
and then run
$ renamer --find '.js' --replace '.ts' 'src/app-ajs/**/*.js'
Create an import chain
After this we need to add imports to bring the new .ts files into the typescript import chain. This might seem tedious, but it does not take especially long time and it is useful to have control of the import order as described below.
Now, it is important that AngularJS modules are created before adding things like service, components, etc. to it. For example, if we have one script file creating the module my-module.js
:
angular.module('myModule',[]);
and one, my-component.js
creating a component in that module:
angular.module('myModule').component('myComponent', ...);
We have to include the my-module.js
script before the my-component.js
script otherwise we will get a runtime failure.
Normally this order is controlled by the order of the script tags in the index.html
.
However, we will no longer load the files through script tags. Instead each file will be loaded through the chain of imports in the typescript files.
This is done by creating index.ts
files, so called barrels, in each catalogue. In each index file the files (modules) in the catalogue are imported in the proper order.
One might wonder why don't we just import from the module file itself like below?
import 'my-component';
angular.module('myModule', []);
The reason for this is that the runtime loader (webpack) executes the import statements before executing the rest of the code in the file, causing the imported files to be executed first, thus calling angular.module('....')
before the module has been created.
The important thing in these index file is thus to import the xxx.module.ts
file before importing the other files (which refers to that module).
We add the following index.ts
files:
src/app-ajs/core/phone/index.ts:
import './phone.module';
import './phone.service';
src/app-ajs/core/index.ts:
import './phone';
import './core.module';
import './checkmark/checkmark.filter';
src/app-ajs/phone-detail/index.ts:
import './phone-detail.module';
import './phone-detail.component';
src/app-ajs/phone-list/index.ts:
import './phone-list.module';
import './phone-list.component';
src/app-ajs/index.ts:
import './core';
import './phone-list';
import './phone-detail';
import './app.module';
import './app.config';
src/main.ts:
Finally we import app-ajs/index
into src/main.ts
thus bringing it into overall import chain. In addition we import AngularJS modules to bring the types into the typescript compiler:
:
// Import these globally to bring in their @types
import 'angular';
import 'angular-resource';
import 'angular-route';
// And import our AngularJS module
import './app-ajs';
:
Now, let's build and see what we get:
$ ng build
which results in the following errors:
ERROR in ./src/app-ajs/phone-list/phone-list.component.ts
Module not found: Error: Can't resolve './phone-list/phone-list.template.html' in '.../angular-phonecat-hybrid/src/app-ajs/phone-list'
@ ./src/app-ajs/phone-list/phone-list.component.ts 6:14-62
@ ./src/app-ajs/phone-list/index.ts
@ ./src/app-ajs/index.ts
@ ./src/main.ts
@ multi ./src/main.ts
ERROR in ./src/app-ajs/phone-detail/phone-detail.component.ts
Module not found: Error: Can't resolve './phone-detail/phone-detail.template.html' in '.../angular-phonecat-hybrid/src/app-ajs/phone-detail'
@ ./src/app-ajs/phone-detail/phone-detail.component.ts 6:14-66
@ ./src/app-ajs/phone-detail/index.ts
@ ./src/app-ajs/index.ts
@ ./src/main.ts
@ multi ./src/main.ts
OK, we need to alter the references in the templateUrl
:s in src/app-ajs/phone-list/phone-list.component.ts
and ./src/app-ajs/phone-detail/phone-detail.component.ts
. Since we are using Angular CLI _and _webpack, these references shall be relative the current file.
Just change them to ./phone-list.template.html
and ./phone-detail.template.html
, respectively and then rebuild.
Now everything should compile.
Configure Angular CLI
Soon we are going to start the application and do our changes in the live server so that we can watch our results. But before doing that we must configure the Angular CLI build.
The Angular CLI build is configured in the .angular-cli.json
file.
Open it and do the following changes.
Add AngularJS scripts
In Angular CLI one adds global libraries by listing them in the script
section. We add the AngularJS script files as follows:
:
"scripts": [
"../node_modules/angular/angular.js",
"../node_modules/angular-resource/angular-resource.js",
"../node_modules/angular-route/angular-route.js"
],
:
Add bootstrap styling
In a similar manner one can add global styling like bootstrap by listing them in the styles
section:
:
"styles": [
"../node_modules/bootstrap/dist/css/bootstrap.css",
"styles.css"
],
:
Add assets
We need to move our assets in src/app-ajs/phones
and src/app-ajs/img
to src/phones
and src/img
to avoid needing to change all URL:s in the code.
Then add these assets to teh configuration:
:
"assets": [
"assets",
"favicon.ico",
"phones",
"img"
],
:
Now we can start the application and do the rest of the changes with the live server
running
$ ng serve
Keep this terminal open to check for build errors.
Then open a browser window and navigate to localhost:4200 where you should see a page with the text:
app works!
However, still no sign of the Phonecat application.
Before moving on, open the developer tools in the browser so that you can see possible errors in the log.
Bootstrap the hybrid
To bring in the AngularJS we must change the bootstrapping of the application.
Open src/main.ts
and change it to this:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { UpgradeModule } from '@angular/upgrade/static';
import { AppModule } from './app/app.module';
// Import these globally to make teh typescript compiler happy by bringing in their @types
import 'angular';
import 'angular-resource';
import 'angular-route';
// Need to import NG 1.x module
import './app-ajs';
platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
// Use the upgrade module to bootstrap the hybrid
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.documentElement, ['phonecatApp']);
});
Also in src/app/app.module.ts
we need to import the UpgradeModule
and override ngBootsas follows:
:
import { UpgradeModule } from '@angular/upgrade/static';
import { HttpModule } from '@angular/http';
:
@NgModule({
:
imports: [
:
HttpModule,
UpgradeModule
]
:
When you save it the code will be re-built and the browser reloads.
No error messages? OK good!
But still no Phonecat... almost there...
Integrate the AngularJS application in the generated Angular app component
When we generated the Angular CLI application we got a ready-made app component: src/app/app.component.ts
with its accompanying template: src/app/app.component.html
.
Let's use that to include our AngularJS Phonecat application by copying the body from src/app-ajs/index.html
(after this you can throw away src/app-ajs/index.html
) and replace the pre-generated content of src/app/app.component.html
:
<div class="view-container">
<div ng-view class="view-frame"></div>
</div>
Save...rebuild...browser reload...
And voilá, now the phonecat application is showing. We only need one finishing touch, the styling from the Phonecat application has not been included.
Open src/styles.css
and add the following:
@import "./app-ajs/app.css";
Again, save...rebuild...browser reload...
And there we are, the Phonecat application up and running as a hybrid Angular+AngularJS application in a Angular CLI project.
Now would be a good time to add some end-to-end tests (unless they already exists) to make sure nothing breaks when we migrate all AngularJS artifacts to Angular counterparts.
Next we will gradually migrate the application to become a true Angular application and get rid of all AngularJS stuff.