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

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>

Related

Angular and AngularJS Hybrid Application Routing: Angular component as child state not rendering

first some short introduction to the project and general setup.
It is an Angular/Angular JS application. I integrated Angular couple of weeks ago. In contrast to many different tutorials using the UpgradeModule, I actually had to use the downgradeModule - The project is quite large and UpgradeModule caused a lot of performance issues.
There is an overall Parent State (called app) and I want a Angular Component to be a child of it. According to the docs this should be possible (https://github.com/ui-router/angular-hybrid#limitations)
Limitations:
We currently support routing either Angular (2+) or AngularJS (1.x) components into an AngularJS (1.x) ui-view. However, we do not support routing AngularJS (1.x) components into an Angular (2+) ui-view.
If you create an Angular (2+) ui-view, then any nested ui-view must also be Angular (2+).
Because of this, apps should be migrated starting from leaf states/views and work up towards the root state/view.
The general setup looks like this (simplification):
app.module.ng1.ts
import { AppModule } from './app.module';
const bootstrapFn: any = (extraProviders: Array<StaticProvider>): any => {
return platformBrowserDynamic(extraProviders).bootstrapModule(AppModule);
};
const downgradedModule: any = downgradeModule(bootstrapFn);
const appModule: angular.IModule = angular
.module('app', [
downgradedModule,
// other project modules
]);
app.module.ts
#NgModule({
imports: [
BrowserModule,
UIRouterUpgradeModule.forChild(),
],
declarations: [
AccountNg2Component,
],
providers: [
],
entryComponents: [
AccountNg2Component,
],
})
class AppModule {
public ngDoBootstrap(): void {}
}
export { AppModule };
TheAccountNg2Component is the one I actually want to go to. account.component.ts
#Component({
selector: 'account',
template,
})
class AccountNg2Component {
#Input() public user: any;
constructor() {}
}
export { AccountNg2Component };
There is a parent app state and I want the AccountNg2Component to be a child of it. The state configuration looks like this:
$stateProvider
.state({
parent: 'app',
name: 'account',
url: '/account',
component: AccountNg2Component,
});
Whatever I try it will also result in the following two Errors:
Transition Rejection($id: 0 type: 6, message: The transition errored, detail: TypeError: Cannot read property 'when' of undefined)
TypeError: Cannot read property 'when' of undefined
at Ng2ViewConfig.load (views.js:47)
at eval (views.js:19)
at Array.map (<anonymous>)
at loadEnteringViews (views.js:19)
at invokeCallback (transitionHook.js:104)
at TransitionHook.invokeHook (transitionHook.js:116)
at eval (transitionHook.js:58)
at processQueue (angular.js:17169)
at eval (angular.js:17217)
at Scope.$digest (angular.js:18352)
at Scope.$apply (angular.js:18649)
at eval (angular.js:18952)
at completeOutstandingRequest (angular.js:6428)
at eval (angular.js:6707)
at ZoneDelegate.invokeTask (zone.js:420)
at Object.onInvokeTask (core.js:4961)
at ZoneDelegate.invokeTask (zone.js:419)
at Zone.runTask (zone.js:187)
at ZoneTask.invokeTask (zone.js:495)
at ZoneTask.invoke (zone.js:484)
at timer (zone.js:2053)
I'm probably missing something in the configuration, but I'm not able to figure it out.
What I already tried:
I looked at the sample App (https://github.com/ui-router/sample-app-angular-hybrid) and tried to build it as similar as possible. But they are using the UpgradeModule instead of the downgrade - I don't know if this changes anything for the router.
I tried
Adding state configuration to UIRouterUpgradeModule.forChild() and UIRouterModule.forChild()
Created a "future state" according to https://github.com/ui-router/sample-app-angular-hybrid/blob/master/app/angularModule.ts#L10
Different ways to declare the Account State
Different ways to define the Account Component itself
The error stays always the same, because of that I think I'm just missing some piece in my configuration.
If my description does not help enough, I'll try to setup a jsfiddle or something similar
Update 1:
Ok, I removed the state declaration for the account state from the Angular 1 State Provider and instead only register it in the UIRouterModule. Now at least the error is gone, but the state is not loaded at all (when trying to access it, redirect to default state)
Ok I finally managed to solve the issue, thanks to a tip from a different article (https://stackoverflow.com/a/49568050/4243635)
Just gonna quote it here again:
The Angular bootstrap module needed a parameter of type "UIRouter" in the constructor, otherwise it would not bootstrap its states:
export class AppModule {
constructor(private router: UIRouter) {
// "router" needed in constructor to bootstrap angular states
}
You also need to import UpgradeModule and UIRouterUpgradeModule. So the entire app.module.ts looks like this:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { ServiceBootstrapComponent } from '../../service-bootstrap';
import { AccountNg2Component } from '../../app/pages/account/account.ng2.component';
import { UIRouterUpgradeModule } from '#uirouter/angular-hybrid';
import { AccountState } from '../../app/pages/account/account.states';
import { CommonModule } from '#angular/common';
import { UIRouter, UIRouterModule } from '#uirouter/angular';
import { UpgradeModule } from '#angular/upgrade/static';
#NgModule({
imports: [
CommonModule,
BrowserModule,
UpgradeModule,
UIRouterUpgradeModule,
UIRouterModule.forChild({states: [AccountState]}),
],
declarations: [
ServiceBootstrapComponent,
AccountNg2Component,
],
providers: [
],
entryComponents: [
ServiceBootstrapComponent,
],
})
class AppModule {
constructor(private router: UIRouter) {}
public ngDoBootstrap(): void {}
}
export { AppModule };

downgradeModule doesn't start Angular5 part in hybrid app

Angular5 introduced a new way of upgrating app from AngularJS to Angular -
downgradeModule. It should resolve an issue with to eager change detection in this kind of hybrid application. So far, in Angular4 I used UpgradeModule successfully, but it caused some performance issue because of change detections. Now I'm trying to use downgradeModule. In this approach AngularJS is started first, next downgradeModule starts Angular. In this way AngularJS in running outside of AngularZone, what should calm down change detection.
main.ts which is pointed in .angular-cli.json as "main"
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app.module';
import {enableProdMode, StaticProvider} from '#angular/core';
import { downgradeModule } from '#angular/upgrade/static';
enableProdMode();
declare var angular: any;
const bootstrapFn = (extraProviders: StaticProvider[]) => {
const platformRef = platformBrowserDynamic(extraProviders);
return platformRef.bootstrapModule(AppModule);
};
const myDowngradedModule = downgradeModule(bootstrapFn);
angular.bootstrap(document.documentElement, [
'legacyApp',
myDowngradedModule
]);
Now, AngularJS starts well, but Angular does not. No error on console, no tips. Just main selector eg. <my-app> is not evaluated.
Nothing changed in app.module.ts in context of previous working version.
#NgModule({
imports: [
CommonModule,
BrowserModule,
UpgradeModule,
RouterModule.forRoot([], { initialNavigation: false })
],
providers: [{provide: APP_BASE_HREF, useValue : '/shop/' }],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule {
ngDoBootstrap() {}
}
I use some draft Angular doc, because the major one doesn't say anything about downgradeModule.
https://pr18487-aedf0aa.ngbuilds.io/guide/upgrade-performance
Does anyone know why Angular5 part is not starting?
Only for information purpose, the previous look of main.ts with UpgradeModule which worked quite well, but change detection.
platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.documentElement, ['legacyApp']);
platformRef.injector.get(Router).initialNavigation();
});
LancerX, have you tried adding AppComponent to the entryComponents in AppModule?
#NgModule({
imports: [
CommonModule,
BrowserModule,
UpgradeModule,
RouterModule.forRoot([], { initialNavigation: false })
],
providers: [{provide: APP_BASE_HREF, useValue : '/shop/' }],
declarations: [ AppComponent ],
entryComponents: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule {
ngDoBootstrap() {}
}
It may be because of the AoT-Compilation. We had a similar Problem and solved it by deactivating aot.
Also see https://pr18487-aedf0aa.ngbuilds.io/guide/upgrade-performance#using-ahead-of-time-compilation-with-hybrid-apps
I had this same issue, and it was because I registered the downgraded component with a bad name. I registered it like this:
angular.module("myModule").directive("my-component", downgradeComponent({ component: MyComponent}));
But it should have been this:
angular.module("myModule").directive("myComponent", downgradeComponent({ component: MyComponent}));
So the component was never initialized because the name was wrong. And because the downgraded module is lazy-loaded (it isn't loaded until one of the components are loaded), the module was never initialized either. Fixing the directive name fixed the problem.

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.

Angular 2 Hybrid App: Unknown provider: ng2.ComponentFactoryRefMapProvider

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!

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