In part 3 of my blog series on AngularJS migration, I go into fine detail on what code changes need to happen in preparation for the migration and how the actual migration is done.
Preparing your Application for Migration
Before beginning to migrate it’s necessary to prepare and align your AngularJS application with Angular. These preparation steps are all about making the code more decoupled, more maintainable, and better aligned with modern development tools.
The AngularJS Style Guide
Ensure that the current code base follows the AngularJS style guide https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md. Angular takes the best parts of AngularJS and leaves behind the not so great parts. If you build your AngularJS application in a structured way using best practices it will include the best parts and none of the bad parts making migration much easier.
The key concepts of the style guide are:
- One component per file. Structuring components in this way will make them easier to find and easier to migrate one at a time.
- Use a ‘folders by feature’ structure so that different parts of the application are in their own folders and NgModules.
- Use Component Directives. In Angular applications are built from components an equivalent in AngularJS are Component Directives with specific attributes set, namely:
- restrict: ‘E’. Components are usually used as elements.
- scope: {} – an isolate scope. In Angular, components are always isolated from their surroundings, and you should do this in AngularJS too.
- bindToController: {}. Component inputs and outputs should be bound to the controller instead of using the $scope.
- controller and controllerAs. Components have their own controllers.
- template or templateUrl. Components have their own templates.
- Use a module loader like SystemJS or Webpack to import all of the components in the application rather than writing individual imports in <script> tags. This makes managing your components easier and also allows you to bundle up the application for deployment.
Migrating to Typescript
The style guide also suggests migrating to TypeScript before moving to Angular however this can also be done as you migrate each component. Information on the recommended approach can be found at https://angular.io/guide/upgrade#migrating-to-typescript however my recommendation would be to leave any migration to Typescript until you begin to migrate the AngularJS components.
Hybrid Routers
Angular Router
Angular has a new router that replaces the one in AngularJS. Both routers can’t be used at the same time but the AngularJS router can serve Angular components while you do the migration.
In order to switch to the new built-in Angular router, you must first convert all your AngularJS components to Angular. Once this is done you can switch over to the Angular router even though the application is still hosted as an AngularJS application.
In order to bring in the Angular router, you need to create a new top-level component that has the <router-outlet></router-outlet> component in it’s template. The Angular.io upgrade guide has steps to take you through this process https://angular.io/guide/upgrade#adding-the-angular-router-and-bootstrap
Angular-UI Router
UI-Router has a hybrid version that serves both AngularJS and Angular components. While migrating to Angular this hybrid version needs to be until all components and services are migrated then the new UI-Router for Angular can be used instead.
To use the hybrid version you will first need to remove angular-ui-router (or @uirouter/angularjs)from the applications package.json and add @uirouter/angular-hybrid instead.
The next step is to add the ui.router.upgrade module to your AngularJS applications dependencies:
let ng1module = angular.module(‘myApp’, [‘ui.router’, ‘ui.router.upgrade’]);
There are some specific bootstrapping requirements to initialise the UI Hybrid Router step by step instructions are documented in the repository’s wiki https://github.com/ui-router/angular-hybrid
Implementation
Bootstrapping a Hybrid Application
In order to run AngularJS and Angular simultaneously, you need to bootstrap both versions manually. If you have automatically bootstrapped your AngularJS application using the ng-app directive then delete all references to it in the HTML template. If you are doing this in preparation for migration then manually bootstrap the AngularJS application using the angular.bootstrap function.
When bootstrapping a hybrid application you first need to bootstrap Angular and then use the upgradeModule to bootstrap AngularJS. In order to do this, you need to create an Angular application to begin migrating to! There are a number of ways to do this, the official upgrade guide suggests using the Angular Quick Start Project however you could also use the Angular CLI. If you don’t know anything about Angular versions 2 and above now is the time to get familiar with the new framework you’ll be migrating to.
Now you should have a manually bootstrapped AngularJS version and a non-bootstrapped Angular version of your application. The next step is to install the @angular/upgrade package so you can bootstrap both versions.
Run npm install @angular/upgrade –save. Create a new root module in your Angular application called app.module.ts and import the upgrade package.
import { NgModule } from '@angular/core' ; import { BrowserModule } from '@angular/platform-browser' ; import { UpgradeModule } from '@angular/upgrade/static' ; @NgModule ({ imports: [ BrowserModule, UpgradeModule ] }) export class AppModule { constructor( private upgrade: UpgradeModule) { } ngDoBootstrap() { this .upgrade.bootstrap(document.body, [ 'angularJSapp' ], { strictDi: true }); } } |
This new app module is used to bootstrap the AngularJS application, replace “angularJSapp” with the name of your AngularJS application.
Finally, update the Angular entry file (usually app.maint.ts) to bootstrap the app.module we’ve just created.
That’s it! You are now running a hybrid application. The next step is to begin converting your AngularJS Directives and Services to Angular versions. The Google walkthrough that these steps are based on can be found at https://angular.io/guide/upgrade#bootstrapping-hybrid-applications
Doing the Migration
Using Angular Components from AngularJS Code
If you are following the Horizontal Slicing method of migration mentioned earlier then you will need to use newly migrated Angular components in the AngularJS version of the application. The following examples are adapted from the official upgrade documentation for more detailed examples see https://angular.io/guide/upgrade#bootstrapping-hybrid-applications
Below is a simple Angular component:
import { Component } from '@angular/core' ; @Component ({ selector: 'hero-detail' , template: ` <h2>Windstorm details!</h2> <div><label>id: </label> 1 </div> ` }) export class HeroDetailComponent { } |
To use this in AngularJS you will first need to downgrade it using the downgradeComponent function in the upgrade package we imported earlier. This will create an AngularJS directive that can then be used in the AngularJS application.
import { HeroDetailComponent } from './hero-detail.component' ; /* . . . */ import { downgradeComponent } from '@angular/upgrade/static' ; angular.module( 'heroApp' , []) .directive( 'heroDetail' , downgradeComponent({ component: HeroDetailComponent }) as angular.IDirectiveFactory ); |
The Angular component still needs to be added to the declarations in the AppModule. Because this component is being used from the AngularJS module and is an entry point into the Angular application, you must add it to the entryComponents for the NgModule.
import { HeroDetailComponent } from './hero-detail.component' ; @NgModule ({ imports: [ BrowserModule, UpgradeModule ], declarations: [ HeroDetailComponent ], entryComponents: [ HeroDetailComponent ] }) export class AppModule { constructor( private upgrade: UpgradeModule) { } ngDoBootstrap() { this .upgrade.bootstrap(document.body, [ 'heroApp' ], { strictDi: true }); } } |
You can now use the heroDetail directive in any of the AngularJS templates.
Using AngularJS Component Directives from Angular Code
In most cases, you will need to use Angular components in the AngularJS application however the reverse is still possible.
If your components follow the component directive style described in the AngularJS style guide then it’s possible to upgrade simple components. Take the following basic component directive:
export const heroDetail = { template: ` <h2>Windstorm details!</h2> <div><label>id: </label> 1 </div> `, controller: function() { } }; |
This component can be upgraded by modifying it to extend the UpgradeComponent.
import { Directive, ElementRef, Injector, SimpleChanges } from '@angular/core' ; import { UpgradeComponent } from '@angular/upgrade/static' ; @Directive ({ selector: 'hero-detail' }) export class HeroDetailDirective extends UpgradeComponent { constructor(elementRef: ElementRef, injector: Injector) { super ( 'heroDetail' , elementRef, injector); } } |
Now you have an Angular component based on your AngularJS component directive that can be used in your Angular application. To include it simply add it to the declarations array in app.module.ts.
@NgModule ({ imports: [ BrowserModule, UpgradeModule ], declarations: [ HeroDetailDirective, /* . . . */ ] }) export class AppModule { constructor( private upgrade: UpgradeModule) { } ngDoBootstrap() { this .upgrade.bootstrap(document.body, [ 'heroApp' ], { strictDi: true }); } } |
Migrating your component directives and services should now be relatively straightforward a detailed example of migrating the Angular Phone Catalogue example, which includes examples of transclusion, can be found at https://angular.io/guide/upgrade#bootstrapping-hybrid-applications
For the most part, if the AngularJS style guide has been followed then the change from component directives to components should simply be a syntax change as no internal logic should need to change. That said there are some services that are not available in Angular and so alternatives need to be found. Below is a list of some common issues that I’ve experienced when migrating AngularJS projects.
Removing $rootScope
Since $rootScope is not available in Angular, all references to it must be removed from the application. Below are solutions to most scenarios of $rootScope being used:
- Services can be used for most events being subscribed to using $rootScope.
- Services can be used to store currently global variables.
- Transition events and hooks from the UI Hybrid Router can be used to replace state change hooks for example $rootScope.$on(‘$stateChangeStart’) see https://ui-router.github.io/ng1/docs/latest/modules/ng1_state_events.html
Removing $compile
Like $rootScope, $compile is not available in Angular so all references to it must be removed from the application. Below are solutions to most scenarios of $compile being used:
- The DomSanitizer module from ‘@angular/platform-browser’ can be used to replace $compileProvider.aHrefSanitizationWhitelist
- $compileProvider.preAssignBindingsEnabled(true) this function is now deprecated. Components requiring bindings to be available in the constructor should be rewritten to only require bindings to be available in $onInit()
- Replace the need for $compile(element)($scope); by utilising the Dynamic Component Loader https://angular.io/guide/dynamic-component-loader.
- Components will need to be re written to remove $element.replaceWith().
Conclusion
In this 3 part blog, we’ve covered the reasons for migrating, the current AngularJS landscape, migration tips and resources, methods for migration, preparing for a migration, different ways of using migrated components and common architectural changes.
The goal of this blog series was to give a comprehensive guide to anyone considering migrating from AngularJS to Angular based on my experience. Hopefully, we’ve achieved this and if your problems haven’t been addressed directly in the blog the links have pointed you in the right direction. If you have any questions please post them in the comments.
AngularJS migration is not an easy task but it’s not impossible! Good preparation and planning are key and hopefully, this blog series will help you on your way.
Sources
- Adding The Angular Router And Bootstrap: https://angular.io/guide/upgrade#adding-the-angular-router-and-bootstrap
- UI-Router angular-hybrid: https://github.com/ui-router/angular-hybrid
- Angular.bootstrap function: https://docs.angularjs.org/api/ng/function/angular.bootstrap
- Angular Quick Start Project: https://angular.io/guide/setup
- Angular CLI: https://cli.angular.io/
You can read part 1 of this series here: https://gofore.com/en/migrating-from-angularjs-part-1/
And you can read part 2 here: https://gofore.com/en/migrating-from-angularjs-part-2/