Angular 2 Hybrid App: Unknown provider: ng2.ComponentFactoryRefMapProvider - angularjs

Scenario
I'm trying to use a very simple angular 2 (rc.5) component as a directive inside my angular 1.5 app but I'm getting an error when adding the directive to one of my modules.
Error
[$injector:unpr] Unknown provider: ng2.ComponentFactoryRefMapProvider <- ng2.ComponentFactoryRefMap <- dummyDirective
Code
Angular2
jspm command for bundling: jspm bundle-sfx embeddedModule.ts dist/component-sfx.min.js --skip-source-maps --format global --global-name __myglobal
This component-sfx.min.js gets moved over into the proper location within the angular1 project
embeddedModule.ts
import 'rxjs/Rx';
import 'core-js/client/shim.js';
import 'zone.js';
import 'reflect-metadata/Reflect';
import { UpgradeAdapter, UpgradeAdapterRef } from '#angular/upgrade';
import { Component, provide, Inject, NgModule, forwardRef } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
const adapter = new UpgradeAdapter(forwardRef(() => EmbeddedModule));
#Component({
selector: 'dummy',
template: '<p>Hello!</p>'
})
class DummyComponent {
constructor() {
console.log('Dummy.constructor...');
}
}
#NgModule({
imports: [
BrowserModule
],
declarations: [
DummyComponent
]
})
class EmbeddedModule {}
var downgradedComponent = adapter.downgradeNg2Component( DummyComponent );
export { adapter, DummyComponent, downgradedComponent };
Angular 1 app
index.html
<script src=... angular vendor scripts ...></script>
<script src="path/to/my/angular2-sfx.js></script>
main.js
angular.element(document).ready(function() {
...
angular.bootstrap(document.body, ['my-app']);
...
});
my.controller.js (where I actually want to use my downgraded component)
(function() {
'use strict';
angular.module('data-gov-reference.state.index')
.controller('MyController', Index)
.directive('dummy', __myglobal.adapter.downgradeNg2Component(DummyComponent));
function Index() {
console.log('MyController...');
}
}());
myview/myview.html (the view where I want the downgraded directive to show up)
<div>
<dummy></dummy>
</div>

Nevermind, figured out the issue.
In main.ts, angular.bootstrap(...) should have been __myglobal.adapter.bootstrap(...)
main.ts
angular.element(document).ready(function() {
...
__myglobal.adapter.bootstrap(document.body, ['my-app']);
...
});
Hope it helps someone else!

Related

Angular migration - Hybrid application is not bootstrapping

I am working on migrating my Angularjs 1.6 application to Angular 7 via ngUpgrade route, after all done when I'm trying to bootstrap my hybrid application, it's failing to bootstrap, I noticed that my main.ts file isn't executing at all where as I've added it as entry point in my webpack config.
I'm using template.ejs file as template in my HtmlWebpackPlugin. For Webpack settings and package.json, Please check my application bundling and bootstrapping code on github here:
https://github.com/mmmathur/AngularMigration
I tried with renaming it as index.ts as well but still no luck.
main.ts
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { UpgradeModule } from '#angular/upgrade/static';
import { AppModule } from '../client/ngApp/ngApp.module';
const app = require('../client/app');
let appRoot = document.createElement('div');
const uiView = document.createElement('ui-view');
let body = document.querySelector('body');
appRoot.appendChild(uiView);
body.appendChild(appRoot);
console.log('executing main.ts');//even this statement is not executed
platformBrowserDynamic().bootstrapModule(AppModule).then((platformRef) => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.documentElement, [app]);
console.log('hybrid application is bootstrapped');
})
.catch(err => console.error(err))
ngAppModule.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import {HttpModule} from '#angular/http';
import { FormsModule} from '#angular/forms';
import {UpgradeModule} from '#angular/upgrade/static';
import { AppComponent } from './ngApp.component';
#NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
UpgradeModule
],
declarations: [
AppComponent
],
bootstrap: [
AppComponent
],
entryComponents: [
]
})
export class AppModule {}
ngApp.component.ts
import { Component } from "#angular/core";
#Component({
selector: 'ng-app',
template: `
<div>
<h2>Angular 7 application bootstraped</h2>
</div>
`
})
export class AppComponent {}
Expected: my Hybrid application gets bootstrapped.
Actual: a blank screen with no error thrown.
UPDATE Issue was these lines:
optimization:
{
splitChunks: {
chunks: 'all'
}
}
Optimization was causing this issue, once I removed above config option from webpack, application at least starting bootstrapping, though this time I'm getting error like:
Uncaught Error: Can't resolve all parameters for ApplicationModule: (?).
at syntaxError (compiler.js:2547)
at CompileMetadataResolver../node_modules/#angular/compiler/fesm5/compiler.js.CompileMetadataResolver._getDependenciesMetadata (compiler.js:17874)
at CompileMetadataResolver../node_modules/#angular/compiler/fesm5/compiler.js.CompileMetadataResolver._getTypeMetadata (compiler.js:17767)
at CompileMetadataResolver../node_modules/#angular/compiler/fesm5/compiler.js.CompileMetadataResolver.getNgModuleMetadata (compiler.js:17635)
at CompileMetadataResolver../node_modules/#angular/compiler/fesm5/compiler.js.CompileMetadataResolver.getNgModuleSummary (compiler.js:17445)
at compiler.js:17559
at Array.forEach (<anonymous>)
at CompileMetadataResolver../node_modules/#angular/compiler/fesm5/compiler.js.CompileMetadataResolver.getNgModuleMetadata (compiler.js:17547)
at CompileMetadataResolver../node_modules/#angular/compiler/fesm5/compiler.js.CompileMetadataResolver.getNgModuleSummary (compiler.js:17445)
at compiler.js:17532
I tried to fix this by adding polyfills.ts file in my project
Polyfills.ts
import 'core-js/es7/reflect';
import 'core-js/client/shim';
import 'zone.js/dist/zone.js';
but still getting same error, I'm using core-js 3.2.1.
looking if someone have idea about what's going wrong with my code?
UPDATE I learned that I supposed to add polyfills.ts file in HtmlWebpackPlugin-->chunks array as well, after adding, it started picking polyfills file and now that error has gone and now my angularjs app also getting bootstrapped along side angular 7 app, however now I'm getting another error like:
Error: [$injector:modulerr] Failed to instantiate module $$UpgradeModule due to:
Error: [$injector:modulerr] Failed to instantiate module {"default":"ui.app"} due to:
Error: [ng:areq] Argument 'module' is not a function, got Object
https://errors.angularjs.org/1.6.10/$injector/modulerr?p0=%24%24UpgradeModule&p1=Error%3A%20%5B%24injector%3Amodulerr%5D%20Failed%20to%20instantiate%20module%20%7B%22default%22%3A%22ec.customer.servicing.ui.app%22%7D%20due%20to%3A%0AError%3A%20%5Bng%3Aareq%5D%20Argument%20'module'%20is%20not%20a%20function%2C%20got%20Object%0A%0Ahttps%3A%2F%2Ferrors.angularjs.org%2F1.6.10%2F%24injector%2Fmodulerr%3Fp0%3D%257B%2522default%2522%253A%2522ec.customer.servicing.ui.app%2522%257D%26p1%3DError%253A%2520%255Bng%253Aareq%255D%2520Argument%2520'module'%2520is%2520not%2520a%2520function%252C%2520got%2520Object%250Ahttps%253A%252F%252Ferrors.angularjs.org%252F1.6.10%252Fng%252Fareq%253Fp0%253Dmodule%2526p1%253Dnot%252520a%252520function%25252C%252520got%252520Object%250A%2520%2520%2520%2520at%2520http%253A%252F%252Flocalhost%253A8080%252Fng1.bundle.js%253F29bb21d813a74a626d53%253A32020%253A12%250A%2520%2520%2520%2520at%2520assertArg%2520(http%253A%252F%252Flocalhost%253A8080%252Fng1.bundle.js%253F29bb21d813a74a626d53%253A33994%253A11)%250A%2520%2520%2520%2520at%2520assertArgFn%2520(http%253A%252F%252Flocalhost%253A8080%252Fng1.bundle.js%253F29bb21d813a74a626d53%253A34004%253A3)%250A%2520%2520%2520%2520at%2520http%253A%252F%252Flocalhost%253A8080%252Fng1.bundle.js%253F29bb21d813a74a626d53%253A36915%253A11%250A%2520%2520%2520%2520at%2520forEach%2520(http%253A%252F%252Flocalhost%253A8080%252Fng1.bundle.js%253F29bb21d813a74a626d53%253A32313%253A20)%250A%2520%2520%2520%2520at%2520loadModules%2520(http%253A%252F%252Flocalhost%253A8080%252Fng1.bundle.js%253F29bb21d813a74a626d53%253A36889%253A5)%250A%2520%2520%2520%2520at%2520http%253A%252F%252Flocalhost%253A8080%252Fng1.bundle.js%253F29bb21d813a74a626d53%253A36907%253A40%250A%2520%2520%2520%2520at%2520forEach%2520(http%253A%252F%252Flocalhost%253A8080%252Fng1.bundle.js%253F29bb21d813a74a626d53%253A32313%253A20)%250A%2520%2520%2520%2520at%2520loadModules%2520(http%253A%252F%252Flocalhost%253A8080%252Fng1.bundle.js%253F29bb21d813a74a626d53%253A36889%253A5)%250A%2520%2520%2520%2520at%2520createInjector%2520(http%253A%252F%252Flocalhost%253A8080%252Fng1.bundle.js%253F29bb21d813a74a626d53%253A36806%253A19)%0A%20%20%20%20at%20http%3A%2F%2Flocalhost%3A8080%2Fng1.bundle.js%3F29bb21d813a74a626d53%3A32020%3A12%0A%20%20%20%20at%20http%3A%2F%2Flocalhost%3A8080%2Fng1.bundle.js%3F29bb21d813a74a626d53%3A36929%3A15%0A%20%20%20%20at%20forEach%20(http%3A%2F%2Flocalhost%3A8080%2Fng1.bundle.js%3F29bb21d813a74a626d53%3A32313%3A20)%0A%20%20%20%20at%20loadModules%20(http%3A%2F%2Flocalhost%3A8080%2Fng1.bundle.js%3F29bb21d813a74a626d53%3A36889%3A5)%0A%20%20%20%20at%20http%3A%2F%2Flocalhost%3A8080%2Fng1.bundle.js%3F29bb21d813a74a626d53%3A36907%3A40%0A%20%20%20%20at%20forEach%20(http%3A%2F%2Flocalhost%3A8080%2Fng1.bundle.js%3F29bb21d813a74a626d53%3A32313%3A20)%0A%20%20%20%20at%20loadModules%20(http%3A%2F%2Flocalhost%3A8080%2Fng1.bundle.js%3F29bb21d813a74a626d53%3A36889%3A5)%0A%20%20%20%20at%20createInjector%20(http%3A%2F%2Flocalhost%3A8080%2Fng1.bundle.js%3F29bb21d813a74a626d53%3A36806%3A19)%0A%20%20%20%20at%20doBootstrap%20(http%3A%2F%2Flocalhost%3A8080%2Fng1.bundle.js%3F29bb21d813a74a626d53%3A33860%3A20)%0A%20%20%20%20at%20Object.bootstrap%20(http%3A%2F%2Flocalhost%3A8080%2Fng1.bundle.js%3F29bb21d813a74a626d53%3A33881%3A12)
at angular.js:125
at angular.js:5034
at forEach (angular.js:418)
at loadModules (angular.js:4994)
at createInjector (angular.js:4911)
at doBootstrap (angular.js:1965)
at Object.bootstrap (angular.js:1986)
at bootstrap (static.js:76)
at static.js:1595
at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:391)
Any idea what's going wrong here?
If you're migrating from AngularJS probably the <script> tag for your bundle is inside the head segment. Move it to the end of body
<html>
<head>
</head>
<body>
... ...
<script src="dist/main.js" charset="utf-8"></script>
</body>
</html>

Angular UpgradeModule: can't get element to bind during app bootstrap

I have an old AngularJS app I'm trying to implement an upgrade-in-place using the Angular 6 UpgradeModule. I can get all the code to execute -- I'm logging out states as expected through both the Angular 6 and AngularJS apps.
The problem is that I'm failing utterly to bind anything to the DOM.
All the documentation and examples use NgDoBootstrap thus, inside the core AppModule of the new Angular 6 app:
this.upgrade.bootstrap(document.body, ['angularJS-app-name'], {strictDi: true});
I can execute that. I can see my AngularJS app bootstrapping (via console.logs) via the UpgradeModule. I can see my Angular 6 app bootstrapped (via console.logs). But nothing is bound to the DOM.
Logging out document gives me the HTML document I'd expect. I can manually examine that in the Chrome console, and see all the elements I would expect to. But all document methods and properties seem to be returning null.
document.body: null.
document.getElementById('an-elementId-I-can-see-when-logging-out-document'): null.
Tell me I'm just doing something dumb like not injecting something properly so that Angular/TS is interpreting document differently than vanilla JS does.
master.app.ts
import {APP_BASE_HREF} from "#angular/common";
import {Component, NgModule, Inject} from '#angular/core';
import {BrowserModule} from '#angular/platform-browser';
import {UpgradeModule} from '#angular/upgrade/static';
import {platformBrowserDynamic} from '#angular/platform-browser-dynamic';
import {RouterModule, Routes, UrlHandlingStrategy} from '#angular/router';
#Component({
selector: 'ng6-router-root',
template: '<router-outlet></router-outlet><div class="ng-view"></div>'
})
export class Ng6RouterRoot{}
export class HybridUrlHandlingStrategy implements UrlHandlingStrategy {
shouldProcessUrl(url: any) {return false;}
extract(url:any) {return url;}
merge(url:any, whole:any) {return url;}
}
#NgModule({
declarations: [
Ng6RouterRoot
],
imports: [
BrowserModule,
UpgradeModule,
RouterModule.forRoot([])
],
providers: [
{ provide: UrlHandlingStrategy, useClass: HybridUrlHandlingStrategy },
{ provide: APP_BASE_HREF, useValue: '/' }
]
})
export class AppModule {
constructor (private upgrade: UpgradeModule) {
}
ngDoBootstrap() {
console.log('master.app.ts ngDoBootstrap start', document);
console.log('document.body', document.body);
this.upgrade.bootstrap(document.getElementById('master'), ['angularJsApp'], {strictDi: true});
console.log('master.app.ts bootstrap end');
}
}
platformBrowserDynamic().bootstrapModule(AppModule);
console.log('master.app.ts end readyState', document.readyState);
relevant html
<div id="master">
<ng6-router-root>
</ng6-router-root>
</div>

Angular UpgradeComponent cannot find $scope

I have a hybrid angular-cli that roughly follows Victor Savkin's Lazy Loaded AngularJS guide. AngularJS is bootstraped in the constructor of a LazyLoaded Angular module. The main difference between my app and the guide is that I am trying to wrap the <ui-view> directive inside of some Angular components. Because of how my layout is structured the <ui-view> element will not be available when AngularJS is bootstrapped and may be added or removed at any time.
import { Component, Directive, ElementRef, Injector } from '#angular/core';
import { UpgradeComponent } from '#angular/upgrade/static';
import * as angular from 'angular';
#Component({
template: `
<layout-wrapper>
<my-toolbar></my-toolbar>
<layout-contents>
<ng2-ui-view>
<h3 class="text-center">AngularJS page not loaded</h3>
</ng2-ui-view>
</layout-contents>
</layout-wrapper>
`,
})
export class LegacyOutputComponent { }
#Directive({selector: 'ng2-ui-view'})
export class UpgradedUiViewComponent extends UpgradeComponent {
constructor(ref: ElementRef, inj: Injector) {
super('uiViewWrapper', ref, inj);
}
}
export const routerPatchModule = 'arcs.router.patch';
// We need to define a wrapper for ui-view because we can only upgrade
// components with only one definition. uiView cannot be automatically
// upgraded because its definition is too complex
angular.module(routerPatchModule, ['ui.router'])
.component('uiViewWrapper', { template: '<ui-view></ui-view>'})
When I run the code a Error: No provider for $scope! error is thrown. Checking the stack trace I can see that it is thrown in the UpgradeComponent super class. The injector tries to get $scope and
Alternative is to let Angular know that it needs to provide the $scope.
import { Injector } from '#angular/core';
// allow $scope to be provided to ng1
export const ScopeProvider = {
deps: ['$injector'],
provide: '$scope',
useFactory: (injector: Injector) => injector.get('$rootScope').$new(),
};
#Directive({
providers: [ ScopeProvider ],
selector: 'ng2-ui-view',
})
export class UpgradedUiViewComponent extends UpgradeComponent {
constructor(ref: ElementRef, inj: Injector) {
super('uiViewWrapper', ref, inj);
}
}
This setup will not work. AngularJS needs to be able to load in the root of your application in order for the scope to be defined correctly.
A better way to approach this problem is to use the <div ui-view> directive in the root of your application (as in the upgrade guide) and then to downgrade a layout component from Angular into AngularJS to wrap your content.

While bootstrapping a hybrid angular 1 + 2 application, how do you initialize the angular 1 injector?

To recreate the issue, I forked the Angular Quickstart plunker.
I have this simple angular 1.x module:
'use strict';
angular.module('testModule', [])
.factory('testService', function($q, $log, $interval, $rootScope){
//Create a service object that we'll build up with methods and eventually
//return.
var service = {};
service.test = 'test';
return service;
});
I have this angular 2 module which references the angular 1.x $injector to get the angular 1.x module as a provider:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { UpgradeModule } from '#angular/upgrade/static';
import { AppComponent } from './app.component';
#NgModule({
imports: [
BrowserModule,
UpgradeModule
],
declarations: [
AppComponent
],
bootstrap: [ AppComponent ],
providers: [{
provide: 'testModule',
useFactory: (i: any) => i.get('testModule'),
deps: ['$injector']
}]
})
export class AppModule {
ngDoBootstrap() {}
}
And I have my angular 2 component which requests the angular 1.x module via dependency injection:
import { Component, Inject } from '#angular/core';
#Component({
selector: 'my-app',
template: `<h1>Hello {{name}}, my name is {{myName}}</h1>`
})
export class AppComponent {
name = 'Angular';
constructor(#Inject('testModule') testModule: any) {
this.myName = testModule.test;
}
}
I then bootstrap the angular 2 module and the angular 1.x module
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ['testModule'], {strictDi: true});
});
I then get this error TypeError: Cannot read property 'get' of undefined
at useFactory which is occurring from within the arrow function specified as the "useFactory" property of the provider definition I have in the angular 2 module
providers: [{
provide: 'testModule',
useFactory: (i: any) => i.get('testModule'),
deps: ['$injector']
}]
I think there is something I'm not understanding about how to initialize the angular 1.x injector in a hybrid angular 1 + 2 application. You can see I am pulling angular 1.x in via script tag, otherwise my angular 1.x module will throw an error about "angular" not being defined, and I suspect the problem may be related to that. If anyone could provide guidance, I'd appreciate it.
Here is the plunker: https://plnkr.co/edit/wdR7K4rDKoF8L0pfQypM?p=info
Here is the error with call stack:
TypeError: Cannot read property 'get' of undefined
at useFactory (https://run.plnkr.co/IgsjEBjD2d29O3YH/app/app.module.ts!transpiled:32:56)
at AppModuleInjector.get (/AppModule/module.ngfactory.js:140:65)
at AppModuleInjector.getInternal (/AppModule/module.ngfactory.js:192:46)
at AppModuleInjector.NgModuleInjector.get (https://unpkg.com/#angular/core/bundles/core.umd.js:8918:48)
at CompiledTemplate.proxyViewClass.AppView.injectorGet (https://unpkg.com/#angular/core/bundles/core.umd.js:12319:49)
at CompiledTemplate.proxyViewClass.DebugAppView.injectorGet (https://unpkg.com/#angular/core/bundles/core.umd.js:12699:53)
at CompiledTemplate.proxyViewClass.View_AppComponent_Host0.createInternal (/AppModule/AppComponent/host.ngfactory.js:15:63)
at CompiledTemplate.proxyViewClass.AppView.createHostView (https://unpkg.com/#angular/core/bundles/core.umd.js:12275:25)
at CompiledTemplate.proxyViewClass.DebugAppView.createHostView (https://unpkg.com/#angular/core/bundles/core.umd.js:12683:56)
at ComponentFactory.create (https://unpkg.com/#angular/core/bundles/core.umd.js:7710:29)
at ApplicationRef_.bootstrap (https://unpkg.com/#angular/core/bundles/core.umd.js:8677:61)
at eval (https://unpkg.com/#angular/core/bundles/core.umd.js:8506:93)
at Array.forEach (native)
at PlatformRef_._moduleDoBootstrap (https://unpkg.com/#angular/core/bundles/core.umd.js:8506:46)
at eval (https://unpkg.com/#angular/core/bundles/core.umd.js:8458:31)
I had same issue and found solution for my case on github.
Take a look at updated plunker. Note that there were also others not related issues in you code. Commented <script src="test-module.js"></script> and implicit injection parameters for testService.
Hope it will help you.

Angular2 bootstrap from outside typescript file, directly from html page

I have existing website, which is build with angular1+requirejs and php. I would like to add angular2 (which I have already added, using angular2-cli).
I don't need angular1 to communicate with angular2, so we don't have to do ng-upgrade
I want to bootstrap angular2 components outside app component typescript file.
I'm bit confused on how to do this, because everything is in typescript and after compiled, they are completely different.
I'm trying to load different component per page, sometime one and sometime more then one.
Actually, first to all you need to provide more information. But, on the big picture, you have create a root module and declare component that belong to it, the same for directives and so on. Ex.
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import
{ AppComponent } from './app.component';
#NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule {}
bootstraping the module:
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
anyway, you can check this to get some idea ngModule
This is how to boostrap with javascript.
Creating module:
(function(app) {
app.AppModule =
ng.core.NgModule({
imports: [ ng.platformBrowser.BrowserModule ],
declarations: [ app.AppComponent ],
bootstrap: [ app.AppComponent ]
})
.Class({
constructor: function() {}
});
})(window.app || (window.app = {}));
bootstraping:
(function(app) {
document.addEventListener('DOMContentLoaded', function() {
ng.platformBrowserDynamic
.platformBrowserDynamic()
.bootstrapModule(app.AppModule);
});
})(window.app || (window.app = {}));

Resources