Tutorial

How To Upgrade from AngularJS to Angular with ngUpgrade

Updated on January 26, 2021
Default avatar

By Sam Julien

How To Upgrade from AngularJS to Angular with ngUpgrade

This tutorial is out of date and no longer maintained.

Introduction

Angular (2+) is here, and we’re all super excited about it. For some of us, though, we’re still maintaining large AngularJS (1.x) codebases at work. How do we start migrating our application to the new version of Angular – especially if we can’t afford to take six months away from a feature development for a complete rewrite?

That’s where the ngUpgrade library comes in. ngUpgrade is the official tool to allow you to migrate your application litle by little. It lets Angular run side-by-side along with your AngularJS code for as long as you need to slowly upgrade.

In this guide, you will install and set up ngUpgrade and Angular. Then, you’ll learn the basics of rewriting components.

(P.S. If the length of this guide freaks you out, don’t worry. I’ve built a step-by-step, super detailed video program called Upgrading AngularJS that covers all of this in detail.)

Upgrading AngularJS

Prerequisites

To complete this tutorial, you will need:

This tutorial was verified with Node v14.4.0, npm v6.14.5, angular v1.6.6, and @angular/common v5.2.11.

Step 1 — Starting the Project

To get started with ngUpgrade, your application needs to meet a few prerequisites:

  1. Code organized by feature (not by type) and every file contains only one item (like a directive or service)
  2. TypeScript set up
  3. Using a module bundler (most people use Webpack)
  4. Using AngularJS 1.5+ with controllers replaced by components

For now though, take a minute to clone or fork the course sample project on GitHub:

  1. git clone https://github.com/upgradingangularjs/ordersystem-project.git

Navigate to the project directory:

  1. cd ordersystem-project

Checkout this commit to see our starting point:

  1. git checkout fdfcf0bc3b812fa01063fbe98e18f3c2f4bcc5b4

We’ve got an Order System project that we can use to work through ngUpgrade. Starting at this commit, our application meets all of the above criteria. We’re using component architecture, TypeScript, and Webpack (we’ve even got builds for both development and production).

Note: In many large AngularJS apps, you just can’t move everything into a brand new Git repository and wipe out years of history. You also might be using a different app structure than the CLI. If you can use the CLI for your upgrade, then feel free to do so. This guide, however, will teach you the manual setup here so that you can have complete control over your upgrade.

Step 2 — Installing Angular and ngUpgrade

We’re ready to install Angular, ngUpgrade, and all of the peer dependencies. In the sample project, go ahead and update your package.json dependencies array so it looks like this:

package.json
"dependencies": {
    "@angular/common": "^5.2.5",
    "@angular/compiler": "^5.2.5",
    "@angular/core": "^5.2.5",
    "@angular/forms": "^5.2.5",
    "@angular/platform-browser": "^5.2.5",
    "@angular/platform-browser-dynamic": "^5.2.5",
    "@angular/router": "^5.2.5",
    "@angular/upgrade": "^5.2.5",
    "angular": "1.6.6",
    "angular-route": "1.6.6",
    "bootstrap": "3.3.7",
    "core-js": "^2.5.3",
    "jquery": "^2.2.4",
    "lodash": "4.17.4",
    "moment": "~2.17.1",
    "reflect-metadata": "^0.1.12",
    "rxjs": "^5.5.6",
    "zone.js": "^0.8.20"
}

(We’re going to use Angular 5 in this series, even though the sample project uses version 4, but the steps are identical.)

We could put all of these packages in one long command in the terminal with the save flag, but we will take the time to explain what each of these packages are.

First are our libraries under the @angular namespace:

  • @angular/common: These are the commonly needed services, pipes, and directives for Angular. This package also contains the new HttpClient as of version 4.3, so we no longer need @angular/http.
  • @angular/compiler: This is Angular’s template compiler. It takes the templates and converts them into the code that makes your application run and render. You almost never need to interact with it.
  • @angular/core: These are the critical runtime parts of Angular needed by every application. This has things like the metadata decorators (e.g., Component, Injectable), all the dependency injection, and the component life-cycle hooks like OnInit.
  • @angular/forms: This is everything we need with forms, whether template or reactive.
  • @angular/platform-browser: This is everything DOM and browser related, especially pieces that help render the DOM. This is the package that includes bootstrapStatic, which is the method that we use for bootstrapping our applications for production builds.
  • @angular/platform-browser-dynamic: This package includes providers and another bootstrap method for applications that compile templates on the client. This is the package that we use for bootstrapping during development and we’ll cover switching between the two in another video.
  • @angular/router: As you might guess, this is just the router for Angular.
  • @angular/upgrade: This is the ngUpgrade library, which allows us to migrate our AngularJS application to Angular.

After all of our Angular packages come our polyfill packages that are dependencies of Angular:

  • core-js: Patches the global context or the window with certain features of ES6 or ES2015.
  • reflect-metadata: This is a polyfill library for the annotations that Angular uses in its classes.
  • rxjs: This is the library that includes all of the observables that we’ll use for handling our data.
  • zone.js: THis is a polyfill for the Zone specification, which is part of how Angular manages change detection.

Sometimes, there are conflicts involving the version of TypeScript you’re using. This can be due to RxJS, the Angular compiler, or Webpack. If you start getting weird compilation errors, do some research to find out of any of those need a specific version range of TypeScript for the version you’re using.

Open your terminal, cd into the public folder of the project:

  1. cd public

And use npm to install the dependencies (you’re welcome to install and use Yarn if you’d prefer):

  1. npm install

You will see that all of your packages were installed.

We’re now ready to make our application a hybrid application by dual-booting both AngularJS and Angular.

Step 3 — Setting Up ngUpgrade

To set up ngUpgrade, we need to do a series of steps to allow AngularJS and Angular to run alongside of each other.

Removing Bootstrap from index.html

The first thing we need to do is remove our bootstrap directive from index.html. This is how AngularJS normally gets started up at page load, but we’re going to bootstrap it through Angular using ngUpgrade. So, just open index.html and remove that data-ng-app tag. (If you’re using strict DI in your own app, you’ll remove ng-strict-di as well in this step.)

Your index.html file will look like this now:

public/src/index.html
<html>
  <head>
    <title>Amazing, Inc. Order System</title>
  </head>
  <body>
      <navigation></navigation>
      <div class="container" ng-view></div>
  </body>
</html>

Changing the AngularJS Module

Now, we need to make some changes in AngularJS module. Open up app.ts.

The first thing we need to do is rename app.ts to app.module.ajs.ts to reflect that it’s the module for AngularJS. It’s kind of a lengthy name, but in Angular we want to have our type in our file name. Here we’re using app.module and then we’re adding that ajs to specify that it’s for AngularJS instead of our root app.module for Angular (which we’ll make in a second).

As the app is now, we’re just using AngularJS, so we have all of our import statements here and we’re registering everything on our Angular module. However, now what we’re going to do is export this module and import it into our new Angular module to get it up and running.

So, on Line 28 let’s create a string constant of our app name:

public/src/app.module.ajs.ts
const MODULE_NAME = 'app';

Then we’ll replace our app string with module name in our angular.module declaration:

public/src/app.module.ajs.ts
angular.module(MODULE_NAME, ['ngRoute'])
// component and service registrations continue here

And finally, we need to export our constant:

public/src/app.module.ajs.ts
export default MODULE_NAME;

At this point, changes made to app.module.ajs.ts should resemble this diff.

Creating the Angular App Module

Our AngularJS module is ready to go, so we’re now ready to make our Angular module. We’ll then import our AngularJS module so we can manually bootstrap it here. That’s what let’s the two frameworks run together, and enables ngUpgrade to bridge the gap between them.

The first thing we need to do is create a new file at the same level as our AngularJS module called app.module.ts. Now for the first time, you’re about to see a pattern that’s going to become familiar to you throughout your upgrade: making and exporting a class, decorating it with an annotation, and importing all of the dependencies.

In our new app module, let’s create a class named AppModule:

public/src/app.module.ts
export class AppModule {
}

Now, let’s add our first annotation (also called a decorator). An annotation is just a bit of metadata that Angular uses when building our application. Above our new class, we’ll use the NgModule annotation and pass in an options object:

public/src/app.module.ts
@NgModule({})
export class AppModule {
}

If you’re following along in an editor like Visual Studio Code, you’ll see that TypeScript is mad at us because it doesn’t know what NgModule is. This is because we need to import it from the Angular core library. Above our decorator, we can fix this with:

public/src/app.module.ts
import { NgModule } from '@angular/core';

Now, in our options object for ngModule, we need to pass an array of imports. The imports array specifies other NgModules that this NgModule will depend on. (These imports are different than the TypeScript imports at the top of our file.) Right now, we need the BrowserModule and the UpgradeModule:

public/src/app.module.ts
import { NgModule } from '@angular/core';

@NgModule({
    imports: [
        BrowserModule,
        UpgradeModule
    ]
})
export class AppModule {
}

Of course, we don’t have those imported either at the top of our file, so we need to do that, too. After our first import, we can add:

public/src/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { UpgradeModule } from '@angular/upgrade/static';

There’s an UpgradeModule in both upgrade and upgrade/static. We want to use the static one because it provides better error reporting and works with AOT (ahead-of-time) compiling.

We’ve got the basic scaffolding of our root module for Angular set up and we’re ready to do the bootstrapping itself.

Bootstrapping in the Angular Module

To bootstrap our application, the first thing we need to do is inject UpgradeModule using a constructor function:

public/src/app.module.ts
constructor(private upgrade: UpgradeModule){
}

We don’t need to do anything in our constructor function. The next thing we’ll do is override the doBootstrap function. After the constructor, type:

public/src/app.module.ts
ngDoBootstrap(){
}

Next, we’ll use the UpgradeModule’s bootstrap function. It has the same signature as the Angular bootstrap function, but it does a couple extra things for us. First, it makes sure that Angular and AngularJS run in the correct zones, and then it sets up an extra module that allows AngularJS to be visible in Angular and Angular to be visible in AngularJS. Lastly, it adapts the testability APIs, so that Protractor will work with hybrid apps, which is super important.

Let’s add it:

public/src/app.module.ts
ngDoBootstrap(){
  this.upgrade.bootstrap(document.documentElement, [moduleName], {strictDi: true});
}

We’re first passing in our document element and then our AngularJS module inside an array. Lastly, just so you can see an example of this, we’re adding a config object so we can switch on strict dependency injection.

You may be wondering where the moduleName came from. We need to import it up with our other import statements:

public/src/app.module.ts
import moduleName from './app.module.ajs';

Here’s what our completed app.module.ts file looks like now:

app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UpgradeModule } from '@angular/upgrade/static';
import moduleName from './app.module.ajs';

@NgModule({
    imports: [
        BrowserModule,
        UpgradeModule
    ]
})
export class AppModule {
    constructor(private upgrade: UpgradeModule) { }

    ngDoBootstrap(){
        this.upgrade.bootstrap(document.documentElement, [moduleName], {strictDi: true});
    }
}

This is going to be a pattern that’s going to become familiar to you over time.

Creating main.ts

Now that we’ve got our AngularJS module and our Angular module set up, we need an entry point that’s going to bring these two together and get our application running. Let’s create a new file under our src folder called main.ts.

In main.ts, we need to import a few things, tell Angular which version of AngularJS to load, and then tell it to bootstrap our Angular module. First, we need to import two polyfill libraries and Angular’s platformBrowserDynamic function:

public/src/main.ts
import 'zone.js';
import 'reflect-metadata';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

Why platformBrowserDynamic instead of just platformBrowser? Angular has two ways to compile: a dynamic option and a static option. In the dynamic option (known as just-in-time, or JIT), the Angular compiler compiles the application in the browser and then launches the app. The static option (known as ahead-of-time, or AOT) produces a much smaller application that launches faster. This is because the Angular compiler runs ahead of time as part of the build process. We’re just going to be using the JIT method here along with the Webpack dev server.

Now we need to import both our Angular and AngularJS modules, as well as a method that tells Angular which version of AngularJS to use:

public/src/main.ts
import { setAngularLib } from '@angular/upgrade/static';
import * as angular from 'angular';
import { AppModule } from './app.module';

Now to finish this off, we just need to call setAngularLib and pass in our version of AngularJS, and we need to call platformBrowserDynamic and tell it to bootstrap our app module.

The finished file looks like this:

public/src/main.ts
import 'zone.js';
import 'reflect-metadata';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { setAngularLib } from '@angular/upgrade/static';
import * as angular from 'angular';
import { AppModule } from './app.module';

setAngularLib(angular);
platformBrowserDynamic().bootstrapModule(AppModule);

Now that we’ve got that set up, we just need to change our Webpack entry point in our config.

Updating Webpack

Hopefully, this process of bootstrapping a hybrid application is starting to make sense to you. We have a main.ts file that’s our entry point, which sets up our AngularJS library and bootstraps our Angular module. Then, our Angular module bootstraps our AngularJS module. That’s what let’s both frameworks run alongside each other.

We’re now ready to change our Webpack config so that it’s starting with our main.ts file and not one of our app module files. Open up webpack.common.js (it’s under the webpack-configs folder). Under module.exports for entry, we’ll change our app root to main.ts:

public/webpack-configs/webpack.common.js
entry: {
    app: './src/main.ts',
}

Testing the Application

Now, we’re ready to see our hybrid application in action. You can run the dev server by opening a terminal and running these commands:

  1. # ensure that you are in the `ordersystems-project` directory
  2. cd server
  3. npm install
  4. npm start

In a second terminal session, run these commands:

  1. # ensure that you are in the `ordersystems-project` directory
  2. cd public
  3. npm run dev

You will see that Webpack is loading and that our TypeScript is compiled successfully.

Let’s go check out the browser at localhost:9000. You can see that our application still runs on our dev server.

Awesome, Inc. Internal Ordering System page displaying in a web browser

You might see a couple of warnings in the console about core-js depending on your version, but don’t worry about them, they won’t affect us. You can also open the network tab and see the vendor bundle and app bundle:

Network pane from DevTools displaying a large file size for vendor.bundle.js

The vendor bundle is absolutely huge, and that’s because:

  1. We’re running Webpack dev server, which means it’s not minifying anything.
  2. We’re running Angular in dynamic compiling, so it’s shipping the compiler code to the browser as well.

We’ll fix this downstream when we talk about AOT compiling, but we can navigate around here and see that all of our data is still loading.

We now have Angular and AngularJS running alongside of each other, which means we’ve successfully set up our hybrid application. That means we’re ready to start upgrading our application piece by piece.

Step 4 — Rewriting and Downgrading Your First Component

We’ve got our application bootstrapped and running in hybrid mode, so we’re ready to get started with migrating each piece of our application. One common approach is to pick a route and then start from the bottom up to rewrite each piece, starting with whatever has the least dependencies. This allows us to iteratively upgrade our application so that every point along the way, we have something that’s deployable to production.

Let’s start with the home route because that’s an easy one with just the Home component. We’ll first rename our Home component from home.ts to home.component.ts.

Now, we need to rewrite our Home component as an Angular class. The first thing we need to do is import Component from the Angular core library at the top of our file:

public/src/home.component.ts
import { Component } from '@angular/core'

The next thing we’ll do is convert our function homeComponentController to a class. We can also capitalize it and remove the controller at the end of the name, so that it’s just called HomeComponent. Lastly, let’s get rid of the parenthesis. It looks like this now:

public/src/home.component.ts
class HomeComponent {
    var vm = this;
    vm.title = 'Awesome, Inc. Internal Ordering System';
}

Now, let’s clean up what’s inside the class. We no longer need the declaration of vm since we’re using a class. We can also add a property of title as a string, and move setting the title to a constructor function. Our class looks like this now:

public/src/home.component.ts
class HomeComponent {
    title: string;
    constructor(){
        title = 'Awesome, Inc. Internal Ordering System';
    }
}

We also need to export this class and then delete that export default line.

Now, we need to apply the Component metadata decorator that we imported to tell Angular that this is a component. We can replace the Home component object with the component decorator and an options object:

public/src/home.component.ts
@Component({
}

The first option of our component decorator is the selector. This is just the HTML tag that we’ll use to reference this component, which will just be ‘home’. Note that in Angular, the selector is a string literal. This is different than in AngularJS, where we would name the component in camel case, and then it would translate to an HTML tag with hyphens. Here, we’re going to put exactly the tag that we want to use. In this case, we’re just keeping it to ‘home’, so it doesn’t matter too much. After that, we’ll specify our template, just like we did with AngularJS, so I’ll just say template: template. And believe it or not, that’s all there is to it. Our finished component looks like this:

public/src/home.component.ts
import { Component } from '@angular/core';

const template = require('./home.html');

@Component({
    selector: 'home',
    template: template
})
export class HomeComponent {
    title: string;
    constructor(){
        this.title = 'Awesome, Inc. Internal Ordering System';
    }
}

Note: If you’re working on an application that will use the AOT compiler, you’ll want to use templateUrl instead of what we’re doing here and make some changes to Webpack. This is totally fine for JIT and the development server, though.

Downgrading the Component for AngularJS

We now need to use the ngUpgrade library to “downgrade” this component. “Downgrading” means to make an Angular component or service available to AngularJS. “Upgrading,” on the other hand, means to make an AngularJS component or service available to Angular. We’ll cover that in another article. Luckily, downgrading is super easy.

First, we need to do two things at the top of our file along with our imports. We need to import the downgradeComponent function from the Angular upgrade library declare a variable called angular so we can register this component on our AngularJS module. This looks like this:

public/src/home.component.ts
import { downgradeComponent } from '@angular/upgrade/static';
declare var angular: angular.IAngularStatic;

Downgrading the component is fairly straightforward. Down at the bottom of our component, we’ll register this component as a directive. We’ll pass in our directive name, which is just home, the same as our selector in this case. Then after that, we’ll pass in the downgradeComponent function from ngUpgrade. This function converts our Angular component into an AngularJS directive. Finally, we’ll cast this object as angular.IDirectiveFactory. The finished registration looks like this:

public/src/home.component.ts
app.module('app')
  .directive('home', downgradeComponent({component: HomeComponent} as angular.IDirectiveFactory);

At this point, changes made to home.component.ts should resemble this diff.

Now, we have a downgraded Angular component that’s available to our AngularJS application. You might be wondering why we registered that directive here at the bottom of this file instead of importing and registering it in our AngularJS module TypeScript file. The end goal is to get rid of that file altogether once all of our application is converted, so we want to gradually remove things from that file and then eventually delete it altogether when we uninstall AngularJS. This works great for sample applications or rapid migrations (more on that in a second).

Go ahead and open up app.module.ajs.ts and remove the import of homeComponent on Line 12 and the component registration on Line 37.

At this point, changes made to app.module.ajs.ts should resemble this diff.

A Quick Note on AOT Compiling

This method of downgrading – registering the downgraded component in the component file and removing it from the AngularJS module file – works perfectly well for development or if you plan on quickly rewriting your application before you deploy. However, the Angular AOT compiler for production won’t work with this method. Instead, it wants all of our downgraded registrations in the AngularJS module.

The downgrade is identical, but instead you’d:

  1. Import downgradeComponent in app.module.ajs.ts (you’ve already got angular in there so you don’t need to declare it).
  2. Change the import of homeComponent to import { HomeComponent } from './home/home.component'; since we switched to a named export.
  3. Change the component registration to the exact same directive registration shown above.

You can read more about setting up ngUpgrade for AOT in this article.

Updating the Template

After a component is updated, we need to be sure to update its template so it complies with the new Angular syntax. In this case, there are only minimal changes you must make to homeComponent. We just need to remove $ctrl on Line 2. The template looks like this now:

<div class="row">
    <h1>{{title}}</h1>
</div>

Now, we have a fully functional downgraded Home component in our hybrid application.

Adding to the Angular App Module

Let’s add our new Angular component to our Angular module. Open up app.module.ts. First, we need to just import our HomeComponent after all of our other imports:

public/src/app.module.ts
import { HomeComponent } from './home/home.component';

Now, we need to add HomeComponent to our Angular application. All Angular components must be added to a declarations array of our NgModule. So, after Line 12 in our options object, we’ll add a new array called declarations and add our component:

public/src/app.module.ts
declarations: [
    HomeComponent
]

We also need to create an entryComponents array and add our HomeComponent to that. All downgraded components must be added to this entryComponents array. We’ll add it after our declarations:

public/src/app.module.ts
entryComponents: [
    HomeComponent
]

At this point, changes made to app.module.ts should resemble this diff.

With that, we’re finished.

Step 5 — Testing the Application

Let’s run those same commands as before and make sure our application is still working. Here are those commands again:

  1. # ensure that you are in the `ordersystems-project` directory
  2. cd server
  3. npm start

In a second terminal session, run these commands:

  1. # ensure that you are in the `ordersystems-project` directory
  2. cd public
  3. npm run dev

Head back over to localhost:9000. You can see that our HomeComponent is loading in the browser as a rewritten Angular component!

You can even go look at the Sources tab of Chrome DevTools just to be positive. Open up webpack://, scroll down to ./src/home/home.component.ts, and sure enough, there it is!

DevTools open to display new home.component.ts file

Conclusion

In this tutorial, you installed Angular and ngUpgrade, set up an Angular module, bootstrapped Angular and AngularJS, updated Webpack, and rewrote and downgraded your first component.

To learn more about Angular, check out the Angular topic page.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Sam Julien

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
1 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Near the top, you have an HTML error. Your link anchor to UpgradingAngularJS.com on the image has 2 backslashes. Remove them.

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel