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.
Related
It seems that routes that are registered within ng2+ module using the #ui-router/angular-hybrid are not recognized on initial load/reload. The same route is working if e.g. navigated by typing the url.
I've followed the official upgrade docs from #ui-router/angular-hybrid
Here is the ng2+ AppModule code:
const states: NgHybridStateDeclaration[] = [
{
name: 'x',
url: '/x',
component: XComponent
}
];
enableProdMode();
#NgModule({
imports: [
BrowserModule,
UpgradeModule,
UIRouterUpgradeModule.forRoot({states: states}),
ComponentsModule,
DirectivesModule
],
declarations: [AppComponent],
exports: [],
entryComponents: [],
providers: [],
bootstrap: []
})
export class Ng2AppModule {
constructor(
#Inject(forwardRef(() => UpgradeModule)) private upgrade: UpgradeModule,
#Inject(forwardRef(() => Config)) private config: Config
) {}
ngDoBootstrap() {
this.upgrade.bootstrap(document as any, ['app'], { strictDi: true });
}
}
And the bootstrapping logic:
AppModule.config([
'$urlServiceProvider',
($urlService: UrlService) => $urlService.deferIntercept()
]);
getConfig()
.then((config: Config) => {
console.log('CONFIG::::', config);
AppModule.constant('config', config);
setAngularLib(angular);
platformBrowserDynamic([
{
provide: Config,
useValue: config
}
])
.bootstrapModule(Ng2AppModule)
.then(platformRef => {
platformRef.injector.get<NgZone>(NgZone).run(() => {
const urlService = platformRef.injector.get(UIRouter).urlService;
urlService.listen();
urlService.sync();
});
downgradeSharedComponents(AppModule);
});
})
P.S. Existing angularjs routes are working properly, the problem is with routes registered within the ng2+ module.
Any help is appreciated, thanks ^^
Managed to figure this out on my own and wanted to post the answer in case anyone encounter similar issue.
Turned out we had a weird logic in our old angularjs code that was deferring $urlRouter url synchronization:
$urlRouterProvider.deferIntercept();
...
$urlRouter.sync();
Once removed, issue described above disappeared.
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>
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 };
Currently building a slimmed down version of the app for plunker so I can SHOW you my problem, but in case anyone has any tips off the top of their heads in the mean time, I will attempt to describe my problem first.
I'm using ngUpgrade to start bringing a large application from AngularJS to Angular. I've got a core application running using Angular. Briefly it's set up a little like this:
#Component({
selector: '[my-app]',
template: `
<app-main></app-main>
`
})
export class AppComponent {};
#NgModule({
imports: [
BrowserModule,
UpgradeModule,
],
bootstrap: [AppComponent],
declarations: [
AppComponent
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class AppModule {
constructor(public upgrade: UpgradeModule) { }
}
export const Ng1AppModule = angular.module(
'mainApp',
[
'feature.one'
]
);
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
ref.instance.upgrade.bootstrap(document.body, [Ng1AppModule.name], {});
});
It successfully bootstraps itself and runs a root component which essentially loads the old AngularJS application. So far no major problems.
The AngularJS application has dependencies on a lot of custom feature modules which I now need to convert to Angular.
On one of the feature modules I want to convert a service. It's now an Angular #Injectable built in typescript and it is assigned to and AngularJS module like so:
export const Ng1FeatureModule = angular
.module('feature.one', ['ngCookies'])
.service('UpgradedService', downgradeInjectable(UpgradedService) as any);
This service requires a dependency from a service I have not even converted yet.
Example:
#Injectable()
export class UpgradedService{
public var1: string;
constructor(private nonconvertedNG1Service: NonconvertedNG1Service) {
this.var1 = nonconvertedNG1Service.get();
}
public getVar1() {
return this.var1;
}
}
How do I need to set things up so that my example app uses 'UpgradedService' and UpgradedService is able to use NonconvertedNG1Service?
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 = {}));