Posts

How to upgrade Angular (JS) to Angular 7?

You must have been asleep (for a few years at least) because Angular 7.x is already here and you’re still stuck on AngularJS. Or maybe it’s just that your codebase is so damn large you can’t face wasting years of your life rewriting it all at once? 

Fear not! It’s not that bad. You can (and should!) upgrade to Angular 7 step by step, or you can do it while leaving your old code in place, working with the latest Angular shipping new features.

But how? Simple. By choosing the Hybrid Application path. This means we will be able to expand our old app with the new Angular, running both frameworks at the same time.

In my opinion, the best way to understand any tool is to in fact dig into the code itself.  So I have prepared a small (really the bare minimum!) repository to use as an Angular upgrade example to show off the process of upgrading.

Prerequisites

Well, actually there aren’t that many prerequisites that your project must follow to be able to upgrade.  All the in-depth details are, as always, in Angular Docs.

Also, these are the things you can do if you like but you are not really obliged to do; some are just for helping you get through the upgrading process (like sticking to the style guide).

To name a few:

  1. Following the AngularJS Style Guide 
  2. Using a Module Bundler: but who doesn’t these days?
  3. TypeScript: this is a must, but you probably already know that
  4. Component Directives: just a good step forward you should already be using if you’re on AngularJS >= 1.5

UpgradeModule to the rescue

Have I mentioned the hybrid app already? Because this seems like a good moment to put in a few more words on it.

The Angular Hybrid app is just like any other, but it comes packed with two versions of Angular; the good ol AngularJS and one of the latest/newest Angular versions (you’re probably reading this with somewhere around Angular 7 / 8 out there, or maybe even 9? 10…?).

Angular comes with a handy UpgradeModule which will help us bootstrap the hybrid application.

The next part of this post will cover the tools inside this module to help you bootstrap, upgrade/downgrade the components.

Bootstrapping

While we usually/sometimes bootstrap AngularJS app automagically from HTML, like this:

<!doctype html>
<html ng-app="app">
<head>
  <meta charset="UTF-8">
  <title>ng Hybrid App</title>
</head>
<body>
  <app></app>
</body>
</html>

working with the hybrid application requires bootstrapping manually two modules, one for each ng version (more on this below). So, it’s necessary to replace the auto bootstrap with manual in your ng1.x app:

import * as angular from 'angular';

import { AppComponent } from './components/app/app.component';
import { WhatAmIComponent } from './components/whatAmI/whatAmI.component';

angular
  .module("app",[])
  .component(AppComponent.selector, new AppComponent())
  .component(WhatAmIComponent.selector, new WhatAmIComponent());

angular
  .bootstrap(document, ['app']);

Who doesn’t like more npm dependencies?

First of all, let’s introduce some Angular dependencies into our project, namely:

  • @angular/core: 
  • @angular/common: 
  • @angular/compiler: 
  • @angular/platform-browser: 
  • @angular/platform-browser-dynamic: 
  • @angular/upgrade: 
  • rxjs

These aren’t all Angular comes with, but they’re enough to do an upgrade. You’ll probably install some more while working with the code e.g. @angular/router, @angular/forms and so on.

Besides Angular itself, we’ll need some polyfills:

  • core-js
  • zone.js

You’ll find the current state of the package.json here: https://github.com/kamil-maslowski/ng-hybrid-app/blob/upgrade-packages/package.json.

{
  "name": "hybrid-app",
  "version": "0.0.1",
  "description": "",
  "scripts": {
    "build": "webpack --config webpack.config.js"
  },
  "author": "Kamil Maslowski",
  "license": "ISC",
  "dependencies": {
    "@angular/common": "^7.2.12",
    "@angular/compiler": "^7.2.12",
    "@angular/core": "^7.2.12",
    "@angular/platform-browser": "^7.2.12",
    "@angular/platform-browser-dynamic": "^7.2.12",
    "@angular/upgrade": "^7.2.12",
    "angular": "^1.7.8",
    "core-js": "^3.0.0",
    "rxjs": "^6.4.0",
    "zone.js": "^0.8.29"
  },
  "devDependencies": {
    "@types/angular": "^1.6.54",
    "html-webpack-plugin": "^3.2.0",
    "ts-loader": "^5.3.3",
    "typescript": "^3.4.1",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.3.0"
  }
}

The order of things to upgrade Angular

AJS module

Now we need to make some changes in the project structure. Our old place-where-it-all-started was here, let’s rename it as ajs.module.ts. 

We also need to remove the line:

angular
  .bootstrap(document, ['app']);

Why do we do this? As I’ve already mentioned, we need two modules, AJS will act as our AngularJS module, and the bootstrapping will be handled by the Angular UpgradeModule tools.

App module

Our app.module:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UpgradeModule } from '@angular/upgrade/static';

@NgModule({
  imports: [
    BrowserModule,
  ]
})
export class AppModule {
  constructor(private upgrade: UpgradeModule) { }
  ngDoBootstrap() {
    this.upgrade.bootstrap(document.documentElement, ['app']);
  }
}

namely the place where we will bootstrap the Angular app (now along with AngularJS) is really simple. We just need to import the bare minimum and export a module, overriding the ngDoBootstrap.

Index.ts reinvented

So the main entry point of our app, index.ts, now looks like this

import 'core-js/proposals/reflect-metadata';
import 'zone.js';

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import './ajs.module';
import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

First, we’ll import some polyfills Angular will need to support all the latest mojos not yet in some of the browsers.  For overall info on that check out the Angular Browser Docs

Then platform-browser-dynamic, already covered above, our AngularJS and Angular modules, and well – that’s all we need here.

The last step now is just to bootstrap the app!

platformBrowserDynamic().bootstrapModule(AppModule);

Hybrid app ready to run!

Now we already have a working, hybrid angular application. But no need to take my word for it.  Just run:

{ "build": "webpack --config webpack.config.js" }

and check out yourself the dist folder. So why almost? Because I’d like to also show you how to take your first baby-steps in your fancy new hybrid app. We’re going to write an Angular component, and downgrade it to make it usable inside the AngularJS code. Let’s do it!

First steps in your hybrid app

Let’s create a new component under the src/components. We’ll call it whatIdLikeToBe.component.ts. We’ll create this component in Angular style, exporting the component class annotated with Component from the @angular/core package. As it’s a template (remember we can also use templateUrl instead of the inline template!) we just have to put in a simple string.

You’ll find the source below

import { Component } from '@angular/core';

@Component({
  selector: 'what-id-like-to-be',
  template: `
    <h2>yay! I'm an ng6 component!</h2>
  `
})
export default class WhatIdLikeToBeComponent { }

What next? Next, we want to be able to use our brand new Angular component in our Angular Hybrid App. To be able to do that we have to tell Angular(JS) that we have some component that it needs to downgrade to 1.x format, so…

import * as angular from 'angular';

import { AppComponent } from './components/app/app.component';
import { WhatAmIComponent } from './components/whatAmI/whatAmI.component';
import { downgradeComponent } from '@angular/upgrade/static';
import WhatIdLikeToBeComponent from './components/whatIdLikeToBe/whatIdLikeToBe.component';

angular
  .module("app",[])
  .component(AppComponent.selector, new AppComponent())
  .component(WhatAmIComponent.selector, new WhatAmIComponent())
  .directive(
    'whatIdLikeToBe',
    downgradeComponent({ component: WhatIdLikeToBeComponent })
  );

See what we did there? We downgraded our component to the AngularJS directive specifying a directive name (spot the camelCase here, it’s easy to mix up where to use camelCase vs kebab-case). And that’s all we need! Our Angular component can now be used anywhere in the AngularJS codebase, like this:

export class AppComponent implements ng.IComponentController, ng.IComponentOptions {
  static selector = 'app';

  controller: ng.Injectable = AppComponent;
  template: string = `
    <h1>Upgrade me!</h1>
    <div>
      <what-am-i></what-am-i>
      <what-id-like-to-be></what-id-like-to-be>
    </div>
  `;
}

Once more, spot how we specified the camelCase directive name vs how we use it with kebab-case here!

Conclusions on Angular upgrade

Reading the intro you were probably thinking:  “Wait a moment! How to upgrade to Angular 7? But we’re already on v. 8/9/2802 ”. And yes, you’d be completely right, which shows how often Angular is updated (in fact there’s a major new version every six months). So there’s no time to lose to start upgrading!

You may, of course, be happy with the current source code that backs your projects, but as time passes, and literally everyday tech stacks develop more and more, it’s worth checking out what’s out there. Not only for the sake of developers, but also for the sake of your end-users, whose user experience will be improved for numerous reasons, not least because of the performance boosts you can achieve and all the mojos made available to you by the Angular team.

Useful links