I have just setup a hybrid AngularJS / Angular 5 application with downgradeModule. I have converted a very small component from AngularJS to Angular but it is never being created. I have put console.logs throughout the component to see if certain bits are called and others not. The file is loaded but the component never is.
I have read what feels like hundreds of tutorials but I must be missing something.
Note that I got this far in converting the component, realised it was not being created, then stopped porting the rest. Hence why driverImage is not currently on the converted component.
Here is a stackblitz with a test component that shows it not working https://angularjs-q1vrpe.stackblitz.io/
Bootstrap
import * as angular from "angular";
import { downgradeModule } from "#angular/upgrade/static";
import { platformBrowserDynamic } from "#angular/platform-browser-dynamic";
import { StaticProvider } from "#angular/core";
import { MainAngularModule } from "./app.module";
import "./index.module";
const bootstrapFn = (extraProviders: StaticProvider[]) => {
console.log("1"); <--- never output!
const platformRef = platformBrowserDynamic(extraProviders);
return platformRef.bootstrapModule(MainAngularModule);
};
const downgradedModule = downgradeModule(bootstrapFn);
angular.module('angularJsModule', ['myApp', downgradedModule]);
angular.bootstrap(document, ['angularJsModule']);
App.Module (MainAngularModule)
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { UpgradeModule } from '#angular/upgrade/static';
#NgModule({
imports: [
BrowserModule,
UpgradeModule
],
declarations: [Ng2TestComponent, DriverImageComponent],
entryComponents: [Ng2TestComponent, DriverImageComponent]
})
export class MainAngularModule {
// Empty placeholder method to satisfy the `Compiler`.
ngDoBootstrap() { }
}
index.module (Stripped most of the dependencies out for brevity)
angular
.module("myApp", [
"constants",
"ngMaterial",
"ngSanitize", ... ]);
The newly converted Component:
import { Component, Input, OnInit, OnChanges } from '#angular/core';
import { IDriver } from "../interfaces/IDriver";
console.log("here"); // --> This is hit
#Component({
selector: "driver-image",
template: `<div class="driver-image" ng-style="{'background-image' : 'url('+$ctrl.driverImage+')'}"></div>`
})
export class DriverImageComponent implements OnInit, OnChanges {
#Input() driver: IDriver;
#Input() small: boolean;
constructor() {
console.log("1"); // --- Not hit
}
ngOnInit() {
console.log("ONINITNINTT"); // --> Not hit
}
ngOnChanges() { }
}
The related modules.ts
import { downgradeComponent } from "#angular/upgrade/static";
...
.component("driverImage", downgradeComponent({ component: DriverImageComponent }))
The answer is actually really simple, it just took me forever to see it.
Contrary to what you might believe, you need to change your AngularJS component to be registered as a directive, NOT, a component.
without a Stackblitz this is pretty hard to reproduce (btw do you have any errors in the console?). I just write down some suggestions that you can try. If you tell me what worked, I'll update my answer
instead of import { platformBrowser } from "#angular/platform-browser"; use this one instead import { platformBrowserDynamic } from '#angular/platform-browser-dynamic'; (can't quite tell you the difference but platform-browser does not work in my projects)
Ok while writing this I think I found the answer. Your problem is you are not loading the component that should be downgraded in your Angular 5 application - everything you want to downgrade still needs to be properly loaded in Angular 5 itself. So try adding a entryComponents: [DriverImageComponent] to your App.Module
Edit:
Ok based on your comment it looks like your Application is not even bootstrapped. Are you sure you are using your component on the page that you are currently using? If the component is not being used, it will not be loaded and Angular won't be bootstrapped.
There are certain scenarios where you need to force this bootstrapping in the beginning.
Create an Bootstrap component
import { Component } from '#angular/core';
#Component({
selector: 'my-bootstrap',
template: '',
})
export class BootstrapComponent {}
Add it to your App.Module
declarations: [
BootstrapComponent,
],
entryComponents: [
BootstrapComponent,
],
Force it to be loaded in your main index.html by adding <my-bootstrap></my-bootstrap in the beginning of the <body>
Related
I'm trying to group multiple Angular 9 Modules into a downgrade Module for AngularJS. I can get it to work fine for just one, but I'm having trouble getting it to work for multiple. I keep getting these errors:
With downgradedModule: specified
Error while instantiating component 'ExampleComponent': Unable to find the specified downgraded module.
Without downgradedModule: specified:
Error while instantiating component 'ExampleComponent': 'downgradedModule' not specified.
I've tried changed the value in downgradedModule to 'SharedModule', 'sharedModule', 'shared-module', and SharedModule with no luck. I'm not sure what else to do here.
Note: All of the manual bootstrapping is done. Not including it in my code samples because that part is working fine. Like I said, this works fine when I only downgrade one module
ngx.module.ts:
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { downgradeModule, downgradeComponent } from '#angular/upgrade/static';
import { SharedModule } from './shared/shared.module';
import { ViewerModule } from './viewer/viewer.module';
import { ExampleComponent } from './shared/example/example.component';
const sharedModule = (extraProviders) => platformBrowserDynamic(extraProviders).bootstrapModule(SharedModule);
const viewerModule = (extraProviders) => platformBrowserDynamic(extraProviders).bootstrapModule(ViewerModule);
export const NgxModule = angular
.module('ngx', [
downgradeModule(viewerModule),
downgradeModule(sharedModule)
])
.directive('exampleComponent',
downgradeComponent({ component: ExampleComponent, downgradedModule: 'sharedModule' })) // error
.name;
shared.module.ts:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { CommonModule } from '#angular/common';
import { ExampleComponent } from './example/example.component';
#NgModule({
imports: [
BrowserModule,
CommonModule
],
declarations: [
ExampleComponent
],
entryComponents: [
ExampleComponent
]
})
export class SharedModule {
constructor() { }
ngDoBootstrap() {
}
}
viewer.module.ts:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { CommonModule } from '#angular/common';
#NgModule({
imports: [
BrowserModule,
CommonModule
],
declarations: [
PdfComponent
],
entryComponents: [
PdfComponent
]
})
export class ViewerModule {
constructor() { }
ngDoBootstrap() {
}
}
I was able to work on this over the weekend and found my solution from reading the documentation a little more closely:
downgradedModule?: string: The name of the downgraded module (if any) that the component "belongs
to", as returned by a call to downgradeModule(). It is the module,
whose corresponding Angular module will be bootstrapped, when the
component needs to be instantiated.
Source: https://angular.io/api/upgrade/static/downgradeComponent
One pitfall others might run into is that you can't call downgradeModule() directly when assigning it to the downgradedModule property. You have to downgrade it first and store it in a variable first, then pass it in.
const ng2BootstrapFn = (extraProviders) => platformBrowserDynamic(extraProviders)
.bootstrapModule(SharedModule);
const downgradedSharedModule = downgradeModule(ng2BootstrapFn);
export const NgxModule= angular
.module('ngx', [downgradedSharedModule ])
.directive('exampleComponent', downgradeComponent({
component: ExampleComponent,
downgradedModule: downgradedSharedModule }))
.name;
What I'm trying to do is to make Angular 2 simple component run inside angular 1 application. I was going through this official guide.
I've faced some issue with injection:
Unknown provider: $$angularInjectorProvider <- $$angularInjector
The stack trace is making no sense, but is obvious that error is raised somewhere deep in the angular itself :)
The structure of my current app looks like this:
ng1.module.ts (entry point):
'use strict';
import { downgradeComponent } from '#angular/upgrade/static';
const angular = require('./lib/angular-wrapper');
const app = angular.module('application', []);
import { AppComponent } from './components/app/app.component.ts';
import { Ng2Module } from './ng2.module.ts';
app.directive(
'app',
downgradeComponent({component: AppComponent}) as angular.IDirectiveFactory
);
angular.bootstrap(document.body, ['application']);
ng2.module.ts:
import 'reflect-metadata';
import '#angular/core';
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { UpgradeModule } from '#angular/upgrade/static';
import { AppComponent } from './components/app/app.component.ts';
#NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent ],
entryComponents: [ AppComponent ]
})
export class Ng2Module {
ngDoBootstrap() {}
}
And app.component.ts:
import 'reflect-metadata';
import { Component } from '#angular/core';
#Component({
selector: 'app',
template: "<h1>HELLO WORLD!</h1>"
})
export class AppComponent {}
Asking for any idea on: what can cause the described above error?
This is caused by the UpgradeModule downgraded service that you are using here:
import { UpgradeModule } from '#angular/upgrade/static';
You are using it because you want the UpgradeModule to downgrade an Angular 2 component to angular JS.
If you dig into the code of the UpgradeModule you can find that this module defines a new angular module named $$UpgradeModule.
This module registers a value provider named $$angularInjector (the one in the error above) - this $$angularInjector thing is responsible for injecting Angular modules into angular JS.
The solution is to import the module in the imports statement so that angular JS will have access to its services.
You forgot to import the UpgradeModule. Here is the answer from the official documentation:
#NgModule({
declarations: [Ng2HeroesComponent, Ng1HeroComponentWrapper],
providers: [
HeroesService,
// Register an Angular provider whose value is the "upgraded" AngularJS service
{provide: 'titleCase', useFactory: (i: any) => i.get('titleCase'), deps: ['$injector']}
],
// All components that are to be "downgraded" must be declared as `entryComponents`
entryComponents: [Ng2HeroesComponent],
// We must import `UpgradeModule` to get access to the AngularJS core services
imports: [BrowserModule, UpgradeModule]
})
class Ng2AppModule {
ngDoBootstrap() { /* this is a placeholder to stop the boostrapper from complaining */
}
}
so first you need to change your code to:
ng2.module.ts:
import 'reflect-metadata';
import '#angular/core';
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { UpgradeModule } from '#angular/upgrade/static';
import { AppComponent } from './components/app/app.component.ts';
#NgModule({
imports: [ BrowserModule, UpgradeModule ],
declarations: [ AppComponent ],
entryComponents: [ AppComponent ]
})
export class Ng2Module {
ngDoBootstrap() {}
}
Also in order to downgrade your component from ng2 to angular 1
You must create an AngularJS directive that will make this Angular component available inside AngularJS templates:
ng1AppModule.directive('Ng2Module', downgradeComponent({component: AppComponent}));
function downgradeComponent(info: { component: Type< This parameter is no longer used */ selectors?: string[]; }): any;
There is a very helpful post which explains in details how to create a hybrid angular application, and also the scenario when you have a v4 component and you want to use it in the v1 template.
I'm migrating an AngularJS project to Angular 5 using ngUpgrade but I'm running into a problem when trying to inject an AngularJS service in one of my new components.
I followed Angular's upgrade guide and created a serviceProvider that uses the $injector (see code below) but I keep getting this error:
core.js:1449 ERROR Error: Uncaught (in promise): Error: Trying to get the AngularJS injector before it being set.
I suspect I need to use forwardRef somewhere to fix this, but I'm unable to find out how and where (and why).
Following the example of the upgrade guide I created a serviceProvider as follows:
ajs-upgraded-providers.ts:
// The AngularJS service I want to use
import { AuthenticationService } from '../ajs/services/authentication.service';
export function authenticationServiceFactory($injector) {
return $injector.get('authentication');
}
export const authenticationServiceProvider = {
provide: AuthenticationService,
useFactory: authenticationServiceFactory,
deps: ['$injector']
};
Then I provide that to the app's NgModule:
app.module.ts:
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
UpgradeModule,
],
providers: [
authenticationServiceProvider,
],
bootstrap: [
AppComponent,
],
})
export class AppModule {
}
I bootstrap that module using ngUpgrade:
main.ts
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { UpgradeModule } from '#angular/upgrade/static';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
// Import our AngularJS module
import '../ajs/app';
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then(platformRef => {
// Use the upgrade module to bootstrap the hybrid
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.documentElement, ['myAngularJSApp']);
});
If I understand correctly, this should allow me to directly inject the AngularJS service into my component like so:
login.component.ts
import { Component } from '#angular/core';
import { AuthenticationService } from '../ajs/services/authentication.service';
#Component({
selector: 'my-login-page',
templateUrl: './login-page.component.html'
})
export class LoginPageComponent {
constructor(private authenticationService: AuthenticationService) {
console.log('authentication', authenticationService);
}
}
Can I do anything to simplify this even more? I tried to follow the upgrade guide as closely as possible, so why doesn't this work?
You need to add
providers: [AuthenticationService]
in Component declaration, like this:
import { Component } from '#angular/core';
import { AuthenticationService } from '../ajs/services/authentication.service';
#Component({
selector: 'my-login-page',
templateUrl: './login-page.component.html',
providers: [AuthenticationService]
})
export class LoginPageComponent {
constructor(private authenticationService: AuthenticationService) {
console.log('authentication', authenticationService);
}
}
I have the most basic angular 2 plunk but no errors are shown to give any indication of a problem. Why can't I see any template output from the child components? Is this a Plunker Issue or is it me?
Plunker version here with Index.html
PARENT: hello_world.ts
import {Component} from 'angular2/core';
import {Jupiter} from './jupiter';
import {Uranus} from './uranus';
#Component({
selector: 'hello-world',
template: `
<p>Can you see jupiter?</p>
<jupiter></jupiter>
<p>Can you see Uranus?</p>
<uranus></uranus>
`
})
export class HelloWorld {
}
CHILD: jupiter.ts
import {Component} from 'angular2/core';
#Component({
selector: 'jupiter',
template: `<p>Hello I'm Jupiter</p>`
})
export class Jupiter {}
CHILD: uranus.ts
import {Component} from 'angular2/core';
#Component({
selector: 'uranus',
template: `<p>Hello I'm Uranus Lol</p>`
})
export class Uranus {}
I've used my demo and used your code to get your expected results that you can see here: https://embed.plnkr.co/VKrvg4/
SystemJS Bootstrap Process
Basically, I've created a main.ts file that is used by SystemJS and then the code in this file bootstraps Angular with the help of AppModule.
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
Declaration
This app module is responsible to declare the components that you would like to register in your Angular application using declarations.
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { AppComponent } from './app.component';
import { HelloWorldComponent } from './hello-world.component'
import { JupiterComponent } from './jupiter.component'
import { UranusComponent } from './uranus.component'
#NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent, HelloWorldComponent, JupiterComponent, UranusComponent],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Bootstrap
The same module also uses AppComponent to bootstrap your Angular application in the code above. The component registered here will be rendered as parent component.
Component Rendering Process
When the app component is rendered, its template uses two other components. Now Angular is aware of your other components as it was registered using declarations.
import { Component } from '#angular/core';
#Component({
selector: 'hello-world',
template: `
<p>Can you see jupiter?</p>
<jupiter></jupiter>
<p>Can you see Uranus?</p>
<uranus></uranus>
`
})
export class HelloWorldComponent {
}
You should be able to see your other component UI in view.
What I'm trying to do is to make Angular 2 simple component run inside angular 1 application. I was going through this official guide.
I've faced some issue with injection:
Unknown provider: $$angularInjectorProvider <- $$angularInjector
The stack trace is making no sense, but is obvious that error is raised somewhere deep in the angular itself :)
The structure of my current app looks like this:
ng1.module.ts (entry point):
'use strict';
import { downgradeComponent } from '#angular/upgrade/static';
const angular = require('./lib/angular-wrapper');
const app = angular.module('application', []);
import { AppComponent } from './components/app/app.component.ts';
import { Ng2Module } from './ng2.module.ts';
app.directive(
'app',
downgradeComponent({component: AppComponent}) as angular.IDirectiveFactory
);
angular.bootstrap(document.body, ['application']);
ng2.module.ts:
import 'reflect-metadata';
import '#angular/core';
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { UpgradeModule } from '#angular/upgrade/static';
import { AppComponent } from './components/app/app.component.ts';
#NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent ],
entryComponents: [ AppComponent ]
})
export class Ng2Module {
ngDoBootstrap() {}
}
And app.component.ts:
import 'reflect-metadata';
import { Component } from '#angular/core';
#Component({
selector: 'app',
template: "<h1>HELLO WORLD!</h1>"
})
export class AppComponent {}
Asking for any idea on: what can cause the described above error?
This is caused by the UpgradeModule downgraded service that you are using here:
import { UpgradeModule } from '#angular/upgrade/static';
You are using it because you want the UpgradeModule to downgrade an Angular 2 component to angular JS.
If you dig into the code of the UpgradeModule you can find that this module defines a new angular module named $$UpgradeModule.
This module registers a value provider named $$angularInjector (the one in the error above) - this $$angularInjector thing is responsible for injecting Angular modules into angular JS.
The solution is to import the module in the imports statement so that angular JS will have access to its services.
You forgot to import the UpgradeModule. Here is the answer from the official documentation:
#NgModule({
declarations: [Ng2HeroesComponent, Ng1HeroComponentWrapper],
providers: [
HeroesService,
// Register an Angular provider whose value is the "upgraded" AngularJS service
{provide: 'titleCase', useFactory: (i: any) => i.get('titleCase'), deps: ['$injector']}
],
// All components that are to be "downgraded" must be declared as `entryComponents`
entryComponents: [Ng2HeroesComponent],
// We must import `UpgradeModule` to get access to the AngularJS core services
imports: [BrowserModule, UpgradeModule]
})
class Ng2AppModule {
ngDoBootstrap() { /* this is a placeholder to stop the boostrapper from complaining */
}
}
so first you need to change your code to:
ng2.module.ts:
import 'reflect-metadata';
import '#angular/core';
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { UpgradeModule } from '#angular/upgrade/static';
import { AppComponent } from './components/app/app.component.ts';
#NgModule({
imports: [ BrowserModule, UpgradeModule ],
declarations: [ AppComponent ],
entryComponents: [ AppComponent ]
})
export class Ng2Module {
ngDoBootstrap() {}
}
Also in order to downgrade your component from ng2 to angular 1
You must create an AngularJS directive that will make this Angular component available inside AngularJS templates:
ng1AppModule.directive('Ng2Module', downgradeComponent({component: AppComponent}));
function downgradeComponent(info: { component: Type< This parameter is no longer used */ selectors?: string[]; }): any;
There is a very helpful post which explains in details how to create a hybrid angular application, and also the scenario when you have a v4 component and you want to use it in the v1 template.