Migrate AngularJS to Angular using UpgradeModule (Part 2)

Danny Cornelisse
24-10-2018

Refactoring angularJS to Angular

If all the steps in part 1 are successful, the old angularJS files can be refactored to Angular files, written in Typescript. It is best to do this incrementally and by file type. The following chapters give a refactor strategy per (file) type.

Refactor constants and values

AngularJS constants and values do not exist anymore in Angular. Use Typescript instead. Use the angular-cli to create app.constants.service.js:

For every constant or value, add a member to the class:

export class Constants {
  public static DEVELOPMENT_MODE = true;
  public static GRAPHS_API = 'http://my.site.com/api';
  public static UPDATE_INTERVAL_DATA = 10000;
}

For each of these constants, create a angularJS constant in the same file:

declare var angular: angular.IAngularStatic;

angular
  .module('app')
  .constant('DEVELOPMENT_MODE', Constants.DEVELOPMENT_MODE)
  .constant('GRAPHS_API', Constants.GRAPHS_API)
  .constant('UPDATE_INTERVAL_DATA', Constants.UPDATE_INTERVAL_DATA)

This makes sure that the constants can be used in both the old angularJS code, as wel as the Angular  app. To use the constants in the angular app, import the class in app.module.ts:

import { Constants } from './app.constants.service';

@NgModule({
  providers: [ Constants ],
  …
})
export class AppModule {
  constructor(private upgrade: UpgradeModule) { }
  ngDoBootstrap() {
    this.upgrade.bootstrap(document.body, [app.name], { strictDi: false });
  }
}

To use constants in another service or component, import the Constants class in the file.

import { Constants } from './../app.constants.service';

However, it is not possible to inject them in the constructor of the class. Use them by directly accessing the class:

if (Constants.DEVELOPMENT_MODE) {
  // Do stuff…
}

Refactor services, factories and providers

Services are probably the easiest to refactor from angularJS to Angular (apart from constants). Services are dependency injections for components (controllers in angularJS) and other services. Because they are dependencies it is wise to refactor them first. However, there is also an order to consider when refactoring. Like components, services also can inject dependencies. Therefore, first refactor the services that have the least dependencies. This will save a lot of overhead.

The strategy to refactor a service:

  1. Remove IFEE and rename to .ts file
  2. Import Injectable, Inject (angular core), downgradeInjectable from ngUpgrade
    import { Injectable, Inject } from '@angular/core';
    declare var angular: angular.IAngularStatic;
    import { downgradeInjectable } from '@angular/upgrade/static';
    
  3. Refactor function into class:
    // angularJS
    
    function NewService ($scope) {
    
    // Angular
    @Injectable() // Don't forget to use the @Injectable decorator
    export class NewService {
    
  4. 4. For all angularJS services (either own services or native angularJS services), inject them in in the constructor of the class using the @Inject() decorator:
    constructor (
       @Inject('$q') private $q,
       @Inject('MyOldService') private myOldService,
       Private myNewService: MyNewService // this is a typescript service
    ) { 
      // Do constructor stuff 
    }
    

    For all angularJS services, you need to add them as a provider in app.module.ts. This is needed to use angularJS services in Angular Services and components:

    @NgModule({
      providers: [
       {
          provide: '$q',
          useFactory: ($injector: any) => $injector.get('$q'),
          deps: ['$injector']
      },
      {
          provide: '$MyOldService',
          useFactory: ($injector: any) => $injector.get('$MyOldService '),
          deps: ['$injector']
      },
      ],
      …
    })
    
  5. Refactor all methods in the angularJS service:
    1. If method is bound to this keyword:
      Change this.firstMethod to public firstMethod
    2. If variable is scoped to function:
      Change const myVar to readonly myVar
    3. If function is private:
      Change function myPrivateFn to private myPrivateFn
  6. Refactor angularJS factory:
    1. Make all functions that are returned from function public:
      Change return { myPublicFn} to public myPublicFn
    2. Follow steps in step 5
  7. Add types to variables, parameters, return values of functions using typescript Interfaces. If needed, create an interface using the angular-cli and import it.
  8. Downgrade the new Angular service to a angularJS service using downgradeInjectable at the end of the file:
    1. Alternative: import newService into app.module.ajs.ts and downgrade it there.
    angular.module('app')
        .service('newService', downgradeInjectable(NewService));
    
  9. Register service as provider in Angular module (app.module.ts):
    import { NewService } from './service.new';
    
    @NgModule({
      providers: [ NewService ],
      …
    })
    
  10. Find where new service is used in other services in .ts files. Remove @Inject annotation from constructor in those .ts files. Import them as a regular Angular service.
    1. If the old angularJS service was registered as in step 5, remove provider from app.module.ts that uses the angularjs $injector. (not breaking if not done, but cleaner)

Follow these steps until all angularJS services have been upgraded before refactoring components.

LEAVE A REPLY

you might also like