4

I have a use case to migrate a large angularjs application and I want to start by doing this process in a small application first. So for that reason, I've took the tour of heroes angularjs webapp and I started to add angular to it ( by creating a new project with the angular-cli ) and then adding the NgUpgrade module.

The problem that I have now is that the angularjs webapp runs pretty well inside the angular 8 application but the components that belong to angular 8 they are not rendered.

I have the impression that my angular components are not bootstrapped since I've bootsraped manually angularjs but I'm not sure ... when I add explicitly the bootstrap property inside the @NgModules it works only for the angular components but not for angularjs (it makes sense ). So I was thinking maybe I have to upgrade the angularjs components or downgrade the newest angular components but I don't think so.

Here you will find the git repository with the code. Below more information related to my project:

  • angular version: 8.2.8
  • angularJS version: 1.6.10
  • angular-cli: 8.3.6

Project structure

enter image description here

index.html

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Common</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>

<body>
  <app-root></app-root>
  <hero-list></hero-list>
</body>

</html>

index.ts ( angularjs root module)

// initialize root module and make it exportable to be able to bootstrap it
// inside angular
export const heroAppModule = angular.module('heroApp', [
    'ngMaterial',
    'perfect_scrollbar',
    'ngJsTree',
    'ngTagsInput',
    'ui.router',
    'heroApp.underscore'
]).config(['$stateProvider', function ($stateProvider) {
    var heroState = {
        name: 'hero',
        url: '/hero',
        template: '<hero-list></hero-list>'
    };
    $stateProvider.state(heroState);
}]);

/** start: REQUIRE ZONE for angularjs
 * Add angularjs files since they aren't yet fully ES6 modules
 * we use requirejs as module loader
 */
require('./editable-field/editable-field');
require('./hero-detail/hero-detail');
require('./hero-list/hero-list');
require('./underscore/underscore.module');
require('./underscore/underscore.service');
/**
 * end: REQUIRE ZONE for angularjs
 */

app.module.ts ( bootstraping angularjs with NgUpgrade )

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

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { heroAppModule } from './../ngjs/index';
import { HelloworldComponent } from './helloworld/helloworld.component';

@NgModule({
  declarations: [ HelloworldComponent ],
  imports: [
    BrowserModule,
    UpgradeModule
  ] // ,
   // bootstrap: [HelloworldComponent]
})


export class AppModule {
  constructor(private upgrade: UpgradeModule) { }
  ngDoBootstrap() {
      setAngularJSGlobal(angular);
      this.upgrade.bootstrap(document.body, [heroAppModule.name], { strictDi: true });
  }
 }

main.ts

import 'zone.js/dist/zone';  // Included with Angular CLI.
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

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

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

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

hero list component (angularjs)

declare var angular: angular.IAngularStatic;

(function () {
    'use strict';
    angular.module('heroApp').component('heroList', {
        template: require('html-loader!./hero-list.html'),
        controller: HeroListController,
        controllerAs: 'vm'
    });

    HeroListController.$inject = ['$scope', '$element', '$attrs'];

    function HeroListController($scope, $element, $attrs) {
        var vm = this;

        vm.list = [
            {
                name: 'Superman',
                location: 'The sky'
            },
            {
                name: 'Batman',
                location: 'Baticueva'
            }
        ];

        vm.updateHero = function (hero, prop, value) {
            hero[prop] = value;
        };

        vm.deleteHero = function (hero) {
            var idx = vm.list.indexOf(hero);
            if (idx >= 0) {
                vm.list.splice(idx, 1);
            }
        };
    }
})();

app-root component ( file name: helloworld.component)

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

@Component({
  selector: 'app-root',
  templateUrl: './helloworld.component.html',
  styleUrls: ['./helloworld.component.scss']
})
export class HelloworldComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

app-root template

<p>helloworld works!</p>

angular.json ( angular-cli file )

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "common": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        }
      },
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/common",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "aot": false,
            "assets": [
              "src/favicon.ico",
              "src/assets"            ],
            "styles": [
              "bower_components/jstree/dist/themes/default/style.min.css",
              "bower_components/ng-tags-input/ng-tags-input.bootstrap.min.css",
              "bower_components/utatti-perfect-scrollbar/css/perfect-scrollbar.css",
              "bower_components/angular-material/angular-material.css",
              "src/styles.scss"
            ],
            "scripts": [
              "bower_components/jquery/dist/jquery.js",
              "bower_components/angular/angular.js",
              "bower_components/angular-material/angular-material.js",
              "bower_components/angular-animate/angular-animate.js",
              "bower_components/angular-aria/angular-aria.js",
              "bower_components/jstree/dist/jstree.js",
              "bower_components/ng-js-tree/dist/ngJsTree.js",
              "bower_components/ng-tags-input/ng-tags-input.js",
              "bower_components/utatti-perfect-scrollbar/dist/perfect-scrollbar.js",
              "bower_components/angular-perfect-scrollbar/src/angular-perfect-scrollbar.js",
              "node_modules/@uirouter/angularjs/release/angular-ui-router.min.js",
              "node_modules/underscore/underscore.js"
            ]
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb",
                  "maximumError": "10kb"
                }
              ]
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "common:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "common:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "common:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.scss"
            ],
            "scripts": []
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "tsconfig.app.json",
              "tsconfig.spec.json",
              "e2e/tsconfig.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        },
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "common:serve"
          },
          "configurations": {
            "production": {
              "devServerTarget": "common:serve:production"
            }
          }
        }
      }
    }},
  "defaultProject": "common"
}

Result...

Result

Cœur
  • 37,241
  • 25
  • 195
  • 267
Max Becerra
  • 466
  • 10
  • 20
  • try this in your app.module.ts: this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: false}); – Maximilian Both Oct 01 '19 at 15:37
  • Hi @MaximilianBoth, thanks for your comment, yes I've tried but this not work. I can't see how strictDi can help me here since is destinated to control the use of explicit function annotation as described here: https://docs.angularjs.org/api/ng/directive/ngApp#with-ngstrictdi- – Max Becerra Oct 02 '19 at 09:57

3 Answers3

2

Finally I've figured out that the solution that I need will never use both components of each framework side by side at the root of my application. Instead, I've used the following pattern as the angular documentation says:

Once you're running a hybrid app, you can start the gradual process of upgrading code. One of the more common patterns for doing that is to use an Angular component in an AngularJS context. This could be a completely new component or one that was previously AngularJS but has been rewritten for Angular.

So as my old application is written in angularjs this framework will contain always my root application and also will be the wrapper of all my future features. So If someday I decide to write a new component in Angular 8 this component will live inside angularjs and it will be downgraded to be able to make it work.

Max Becerra
  • 466
  • 10
  • 20
0

Isolate your Angular 1 code and Angular 8 code

<body>
  <app-root></app-root>
<div ng-controller="MyController">
  <hero-list></hero-list>
</div>
</body>
dota2pro
  • 7,220
  • 7
  • 44
  • 79
  • Hello @dota2pro, thank you for your comment, tour of heroes is a component based application so your approach is not possible. Even if I bootstrap a specific div as the angularjs app and the other angular 8 lives outside ( as isolation method ) this not work. – Max Becerra Oct 02 '19 at 09:51
  • Then maybe you can create a working demo of your code that will explain better – dota2pro Oct 02 '19 at 14:06
  • I've added a reference to github with the project if you want to play :) – Max Becerra Oct 03 '19 at 15:23
0

I had a similar issue, even though in my index.html I had this:

<body>
  <app-root></app-root>
</body>

I found that with the setup of the Angular docs for ngUpgrade, the top-level component needs to be the one from Angular.js, not the one from Angular:

<body>
  <hero-list></hero-list>
</body>
Daniel
  • 21,933
  • 14
  • 72
  • 101