Wednesday, June 2, 2021

How to migrate from AngularJS to Angular

You might already know that AngularJS is nearing its end of life. It is time to think about options to update applications that are using AngularJS.

We should migrate versions step by step.

Here, We will discuss on Running AngularJS 1.6 in Angular 5 side by side (AngularJS + Angular v5 hybrid application). once your angularjs 1.5+ application running with angular 5, you can develop new modules in angular 5 and can move modules one by one from angularJs means Upgrade from AngularJS to Angular.

in this migration, we used the following angular CLI and nodeJs versions if not this then you can upgrade/downgrade it

-Angular CLI (1.7.3)
-Node 8.10.0 LTS 

 
 

Step 1:
Create an Angular 5 app using Angular CLI. (Used Node 8.10.0 LTS) using following commands


ng new ng52 --style=scss
cd ng52
ng serve

Now, your angular 5 application will be running

Step 2:
Copy an existing Angular JS app into src/ng1 of Angular 5 app
directory structure may become like as bellow.

   ng52
   ├── e2e
   ├── karma.conf.js
   ├── node_modules
   ├── package.json
   ├── package-lock.json
   ├── protractor.conf.js
   ├── README.md
   ├── src
   |    ├── app
   |    ├── assets
   |    ├── environments
   |    ├── index.html
   |    ├── main.ts
   |    ├── ng1
   |    |    ├── index.html
   |    |    ├── main.ts
   |    |    ├── app
   |    |    |    ├── app.config.ts
   |    |    |    ├── app.controller.ts
   |    |    |    ├── app.error.ts
   |    |    |    ├── app.route.ts
   |    |    |    ├── app.template.html
   |    |    |    ├── app.ts
   |    |    |    ├── assets
   |    |    |    ├── css
   |    |    |    ├── index.ts
   |    |    |    ├── modules
   |    |     |    ...
   |    |     |    ...
   |    |     |    ...
   |    |     |    ...
   |    ├── tsconfig.app.json
   |    ...
   |    ...
   |    ...
   |    ...
   ├── tsconfig.json
   └── tslint.json

Step 3:
Put all assets images and css of angularjs in the assets directory of angular 5.
put all globals CSS and JS and thirds party css and js in .angular.cli.json file.

Make sure you must need to add angular.js script here like as  "../node_modules/angular/angular.js"

and if you are using ui router then also need to add angular-ui-router.js  like as "../node_modules/@uirouter/angularjs/release/angular-ui-router.js",

EX : .angular.cli.json

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "ng52"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "../node_modules/angular/angular-csp.css",
        "../node_modules/angular-material/angular-material.scss",
        "../node_modules/ng-tags-input/build/ng-tags-input.css",
        "styles.scss",
        "../node_modules/textangular/dist/textAngular.css",
        "../src/ng1/app/css/main.scss"
      ],
      "scripts": [
        "../node_modules/angular/angular.js",
        "../node_modules/@uirouter/angularjs/release/angular-ui-router.js",
        "../node_modules/textangular/dist/textAngular-rangy.min.js",
        "../node_modules/textangular/dist/textAngular-sanitize.min.js",
        "../node_modules/textangular/dist/textAngular.min.js"

      ],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  ...
  ...
  ...
  ...
}

Step 4:
install @angular/upgrade as by which we can run both angularjs and angular hybrid application.

npm install @angular/upgrade --save

also copy and past all required third party's dependencies in package.js file of angular 5 from package.js file of angularJS 1.6. then run the following command

npm install

EX: package.json may like as bellow

{
  "name": "ng52",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^5.2.0",
    "@angular/common": "^5.2.0",
    "@angular/compiler": "^5.2.0",
    "@angular/core": "^5.2.0",
    "@angular/forms": "^5.2.0",
    "@angular/http": "^5.2.0",
    "@angular/platform-browser": "^5.2.0",
    "@angular/platform-browser-dynamic": "^5.2.0",
    "@angular/router": "^5.2.0",
    "@angular/upgrade": "^12.0.2",
    "@uirouter/angularjs": "^1.0.29",
    "angular": "1.6.2",
    "angular-animate": "1.6.2",
    "angular-aria": "1.6.2",
    "angular-drag-and-drop-lists": "^2.1.0",
    "angular-fixed-table-header": "^0.2.1",
    "angular-jwt": "^0.1.9",
    "angular-material": "1.1.4",
    "angular-messages": "1.6.2",
    "angular-mocks": "1.6.2",
    "angular-sanitize": "1.6.2",
    "angular-touch": "1.6.2",
    "angular-translate": "^2.11.0",
    "angular-translate-loader-static-files": "^2.10.0",
    "angular-ui-router": "^0.4.2",
    "core-js": "^2.4.1",
    "immutable": "^3.7.6",
    "immutable-angular": "^0.1.1",
    "js-data": "^2.9.0",
    "js-data-angular": "^3.2.1",
    "lodash": "^4.12.0",
    "moment": "^2.12.0",
    "moment-timezone": "^0.5.23",
    "ng-tags-input": "^3.2.0",
    "ngstorage": "^0.3.11",
    "redux": "^3.3.1",
    "rxjs": "^5.5.6",
    "textangular": "^1.5.16",
    "zone.js": "^0.8.19"
  },
  "devDependencies": {
    "@angular/cli": "~1.7.3",
    "@angular/compiler-cli": "^5.2.0",
    "@angular/language-service": "^5.2.0",
    "@types/angular": "^1.8.1",
    "@types/jasmine": "~2.8.3",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "^4.0.1",
    "jasmine-core": "~2.8.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~2.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~4.1.0",
    "tslint": "~5.9.1",
    "typescript": "~2.5.3"
  }
}

Step 5: app.module.ts
Bootstrap AngularJS in Angular means add UpgradeModule and UrlHandlingStrategy like as bellow

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import {RouterModule, UrlHandlingStrategy} from '@angular/router';
import { AppComponent } from './app.component';
import {UpgradeModule } from '@angular/upgrade/static';
import { UrlResolver } from '@angular/compiler';
export class CustomHandlingStrategy implements UrlHandlingStrategy {
  shouldProcessUrl(url) {
    return url.toString().startsWith("/test");
  }
  extract(url) { return url; }
  merge(url, whole) { return url; }
}  
//import { TestComponent } from './test/test.component';

@NgModule({
  declarations: [
    AppComponent,
    TestComponent
  ],
  imports: [
    BrowserModule,
    UpgradeModule,
    RouterModule.forRoot([  
      //{path: 'test', component: TestComponent},
      // {path: 'home', component: HomeComponent},
      // {path: 'privacy-policy', component: PrivacyPolicyComponent},
      // {path: 'terms-conditions', component: TermsConditionsComponent},
      // {path: '**', redirectTo: ''}  // when url is not found than it stucks
    ], {
      useHash:false,
      initialNavigation: true,
      enableTracing:true //debug only
     }
     )
  ],
  providers: [
    {provide: UrlHandlingStrategy, useClass: CustomHandlingStrategy}
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Here, UpgradeModule will be imported to manage and run both angularjs and angular application with the same project and same port/environment.

UrlHandlingStrategy is used for Switch between AngularJS page and Angular 5 page.
CustomHandlingStrategy checks the URL and decides whether the URL passed should be handled by Angular 5, otherwise, simply forward to the AngularJS app.

initialNavigation: true => it true means first check angular 5 routers and if not found then check routing form angularjs

RouterModule.forRoot => this is for angular 5 routings so you can add your angular 5 routings in this.

Step 6: app.component.html

<div>
  <!-- <div class="ng-view"></div> -->
  <router-outlet></router-outlet>
  <div ui-view="content"></div> // if you ui routing views name is available then put this name here like as content
  <!--<div ui-view></div>-->
</div>

Here, you can use <div class="ng-view"></div> if have default angular routers in angularjs 1.6. but if you have used ui router then need to add like as above HTML code.


Step 7: /src/ng1/app/app.ts

your angularjs 1.6 app.ts my be like as bellow.
export default angular.module("myJsApp", ["ui.router","ngAnimate","ngMaterial"]);

this line is useful for use of app.ts because "myJsApp" module name will be used in main.ts of angular 5. it may be different in different web applications.

"use strict";

import * as angular from "angular"
import { ng } from "angular-ui-router";

// Angular Material
import "angular-material";

// Immutable
import "immutable";
import "immutable-angular";

...
...
...
...
...

export default angular.module("myJsApp", ["ui.router","ngAnimate","ngMaterial"]);

angular.module("myJsApp").component("app", {
    controller: AppController,
    controllerAs: "vm",
    templateUrl: "./app.template.html",
});

new AppRoute().register(angular.module("myJsApp"));

// Config
angular.module("myJsApp").constant(Config.id, new Config());

// Error
angular.module("myJsApp").constant(Error.id, new Error());

// Filter
angular.module("myJsApp").filter('getFormattedName', () => { return CustomNameFormatting.getFormattedName });
angular.module("myJsApp").filter('matchCountry', () => { return CustomNameFormatting.matchCountry });
angular.module("myJsApp").filter('dateConvertToPST', () => { return CustomNameFormatting.dateConvertToPST });
angular.module("myJsApp").filter('quesCatType', () => { return CustomNameFormatting.quesCatType });
angular.module("myJsApp").filter('capitalizeFirst', () => { return CustomNameFormatting.capitalizeFirst });

// Factory
angular.module("myJsApp").factory(SpinnerInterceptor.id, ["$q", "$log", "$injector",
    ($q: ng.IQService, logger: ILogService, $injector: ng.auto.IInjectorService) =>
    new SpinnerInterceptor($q, logger, $injector )
]);
angular.module("myJsApp").factory(AutoDisableOnRequestInterceptor.id, ["$q", "$log", "$injector",
    ($q: ng.IQService, logger: ILogService, $injector: ng.auto.IInjectorService) =>
    new AutoDisableOnRequestInterceptor($q, logger, $injector )
]);

// Service
angular.module("myJsApp").service(Pagination.id, Pagination);
angular.module("myJsApp").service(StoreService.id, StoreService);

// Components

angular.module("myJsApp").value('THROTTLE_MILLISECONDS', null).directive(InfiniteScroll.id, ["$rootScope", "$window", "$interval", "THROTTLE_MILLISECONDS",
    ($rootScope:ng.IRootScopeService, $window:ng.IWindowService, $interval:ng.IIntervalService, THROTTLE_MILLISECONDS:any) =>
    new InfiniteScroll($rootScope, $window, $interval, THROTTLE_MILLISECONDS)]);
angular.module("myJsApp").directive(ReadOnly.id, ["authService",
    (authService:AuthService) =>
    new ReadOnly(authService)]);

angular.module("myJsApp").directive(Whenscrollends.id, [() =>new Whenscrollends()]);
angular.module("myJsApp").directive(Spinner.id, [() => new Spinner()]);

angular.module("myJsApp").config(['$httpProvider', ($httpProvider: ng.IHttpProvider) => {
    // $httpProvider.defaults.useXDomain = true    ;
    $httpProvider.interceptors.push(AuthInterceptor.id);
    $httpProvider.interceptors.push(SpinnerInterceptor.id);
    $httpProvider.interceptors.push(AutoDisableOnRequestInterceptor.id);
}]);

angular.module("myJsApp").run(["$state", "$log", "$rootScope", "$location", "authService", "customerService", "groupsService", "publisherService", "campaignService",
    ($state:IStateService, logger:ILogService, $rootScope:IRootScopeService, $location:ILocationService, authService: AuthService, customerService: CustomerService, groupsService : GroupsService, publisherService: PublisherService, campaignService: CampaignService) => {
    logger.debug("Bootstrapped the application...");

    logger.debug("Registered UI-router states: ");
  ...
  ....
  ...
  ...
  ...
  ..
  ....
  ....
  ...
  ...


Step 8: main.ts

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

import { UpgradeModule } from '@angular/upgrade/static';
import * as angular from 'angular';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

import myJsApp from './../src/ng1/app/app';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
 .then(platformRef => {
  console.log("BOOTSTRAPING AngularJS", myJsApp.name);
  const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
  upgrade.bootstrap(document.body, [myJsApp.name], {strictDi: true});
});


Here, we will be imported the app.ts file of angularjs 1. and import your angularJS module ex: myJsApp.
and bootstrap add/ change platformBrowserDynamic function as given.

Step 9:

Run the angular application using following command

 ng serve

 Now, Fix template paths in both JS and HTML. you also need to fix some other errors which are not compatible with angular CLI and typescript.

After running successfully you can add new angular modules and components by using angular 5 commands. and you can also move one by one angularjs 1 modules in angular 5.

Hope, This article is useful for you and can migrate from AngularJS to Angular very easily.

No comments:

Post a Comment