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.

results matching ""

    No results matching ""