Using services in Angular/AngularJS hybrid app (ng-upgrade) - angularjs

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?

Related

How to inject AngularJS dependencies while calling from Angular module

I am trying to host a simple hybrid application (AngularJS + Angular 7). I was able to set the basic and bootstrap both the modules based on the guidelines. I was able to establish the communication from AngularJS to Angular.
I followed the guidelines for injecting AngularJS service in the Angular module ( https://angular.io/guide/upgrade ). However, I see there are few issues that I am stuck with which could be a basic set up which I am trying to understand.
Here is the real issue, When I try to call the AngularJS service from Angular, it fails during the run time stating the dependency injected in AngularJS service is undefined. For ex: $http is not defined.
My question is, do I need to inject this dependency through a provider in the Angular module, I have a provider which injects the service itself to the Angular module. Any help with the guidelines or standards would be helpful.
Here is the format of my AngularJS service. At present, I am modifying this to class and trying to invoke from Angular
function GetUsers( $http, OtherDependency) {
return {
getUsers: getUsers
}
function getUsers(userID, key, phrase) {
//$http is used inside this method
}
In angular, I am injecting this service through a provider and trying to use this service by instantiating through its constructor
Since your stackbliz demo is not runnable, from my project, I just create a Angular SharedModule and inject angularjs services to provider like below:
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
export function getToastrService($injector: any) {
return $injector.get('toastr');
}
export function getLoadingService($injector: any) {
return $injector.get('loadingService');
}
#NgModule({
imports: [
CommonModule
],
exports: [
NgxComponentsModule
],
providers: [
{ provide: 'toastr', deps: ['$injector'], useFactory: getToastrService },
{ provide: 'loadingService', deps: ['$injector'], useFactory: getLoadingService }
]
})
export class NgxSharedModule { }
And then in my Angular business module, import SharedModule, and then only need to use DI in constructor:
constructor(#Inject('toastr') public toastr: any,
private notifyService: NotifyService) { }
Hope it will work for you.

DowngradeModule and downgradeInjectable result in singleton service per module in Angular/AngularJS hybrid app instead of global singleton

The situation:
I am working on a hybrid project with AngularJS as core where the app slowly needs to be upgraded to Angular 9. So the new Angular modules have to be downgraded to be accessable to AngularJS. The AngularJS app consists of multiple modules and submodules. Here just the core(shared) module and the app.module for simplification:
core.ng2.module.ts
#NgModule({
imports: [
CommonModule
],
declarations: [
ModuleBootstrapComponent
],
entryComponents: [
ModuleBootstrapComponent
]
})
export class CoreNg2Module {
constructor() {
}
public ngDoBootstrap(): void {
}
}
let rootInjectorPromise: Promise<Injector>| null = null;
const getRootInjector = (extraProviders: StaticProvider[]) => {
if (!rootInjectorPromise) {
rootInjectorPromise = platformBrowserDynamic(extraProviders)
.bootstrapModule(AppNg2Module)
.then(moduleRef => moduleRef.injector);
}
return rootInjectorPromise;
};
const bootstrapCoreFn = async (extraProviders: StaticProvider[]) => {
const rootInjector = await getRootInjector(extraProviders);
const moduleFactory = await rootInjector.get(Compiler).compileModuleAsync(CoreNg2Module);
return moduleFactory.create(rootInjector);
};
const downgradedCoreNg2Module = downgradeModule(bootstrapCoreFn);
export const coreNg2ModuleName =
angular
.module('core.ng2', [
downgradedCoreNg2Module
])
.directive('moduleBootstrap',
downgradeComponent({
component: ModuleBootstrapComponent,
downgradedModule: downgradedCoreNg2Module
}))
.name;
the app.module.ts
#NgModule({
imports: [
BrowserModule,
CommonModule,
CoreNg2Module,
]
})
export class AppNg2Module {
constructor() {
}
public ngDoBootstrap(): void {
// Don't use this lifecycle hook since the hybrid app is bootstrapped manually inside Bootstrap.ts.
}
}
export const appModuleName = angular
.module('app.ng2', [
coreNg2ModuleName,
]).factory('translateSyncService',
downgradeInjectable(TranslateSyncService))
.name;
The current problem:
I downgraded and bootstraped the module according to the official guide: https://angular.io/api/upgrade/static/downgradeModule#downgrading-multiple-modules to have my app.module as 'root' for my TranslateSyncService. Because according to this https://angular.io/guide/upgrade#how-ngupgrade-works singleton services are shared between AngularJS and Angular IF they are provided in 'root'.
I need the translate-sync-service to synchronize the state of my Angular components with the AngularJS app.
#Injectable({
providedIn: 'root'
})
export class TranslateSyncService {
languageSource = new BehaviorSubject<string>('en');
usedLanguage$ = this.languageSource.asObservable();
constructor() {
}
changeLanguage(language: string) {
this.languageSource.next(language);
}
}
I downgraded the module and service to make it accessable in AngularJS. The problem is that the service is working, but every module gets its own instance of the service instead of a global singleton service. So core module has its own service and every other module I created.
tldr; I end up with a singleton service per module instead of a global one.
My wish:
I want to have an Angular service that is a singleton across the downgraded Angular modules and the AngularJS app.
Anyone has experience with that issue?
I know this is old but I had the same issue. I found an answer that worked for me (similar situation). We use a common pattern of a downgraded module that we inject into the angular JS app. (there are quite a few examples of this elsewhere).
export const downgradedAngularAppModule = angular.module('downgraded-angular-module', [downgradedAngularModule])
.service('SomeAngularXService', downgradeInjectable(SomeAngularXService))
and then in angularJS
export const angularJsModule: ng.IModule = angular.module(angularjsAppModule, [list of angularJS modules, downgradedAngularAppModule.name])
While still in hybrid mode, we are bootstrapping angular inside angular JS. In the singleton angular X service ensure you decorate with:
#Injectable(
{ providedIn: 'root' } // this means app wide singleton
)
If you wish to use the same instance of the service that you have in your angularJS app, in components that you have migrated to angular X, rather than importing the module directly like this:
import { SomeAngularXService } from 'SomeAngularXService.service.ts'
and injecting it in your constructor:
constructor(private someAngularXService: SomeAngularXService)
you should instead use the angularJS instance in your constructor
constructor(#Inject('SomeAngularXService') private someAngularXService: any)
You will need to remember to provide it in your app.module.ts
{
provide: 'SomeAngularXService',
useFactory: ($injector: any) => $injector.get('SomeAngularXService'),
deps: ['$injector']
}

#angular/router not working inside an angular.js application

I'm working on migrating little by little a big angular.js application (that uses ui-router) to angular and I opted by using the angular.js application as a base and migrate the different routes one at a time so that once I'm finished I switch everything at once to angular.
These are the steps I've followed:
Bootstap the angular.js application in my main.ts file:
export function bootstrapAngular(extra: StaticProvider[]): any {
setAngularJSGlobal(angular);
if (environment.production) {
enableProdMode();
}
return platformBrowserDynamic(extra)
.bootstrapModule(AppModule)
.catch(err => console.log(err));
}
const downgraded = angular
.module('downgraded', [downgradeModule(bootstrapAngular)])
.directive('appRoot', downgradeComponent({ component: RootComponent, propagateDigest: false }))
;
angular.bootstrap(document, ['app', downgraded.name]);
Inside my index.html
<app id="app"></app>
This works fine.
Inside my main angular.js component I add the tag of my downgraded main Angular component:
<div class="app__view" id="appContent">
<ui-view></ui-view>
<app-root></app-root>
</div>
This is how my main module is configured
const COMPONENTS = [
TestComponent,
RootComponent,
];
#NgModule({
declarations: COMPONENTS,
imports: [
BrowserModule,
NxModule.forRoot(),
RouterModule.forRoot(routes, {
initialNavigation: 'enabled',
useHash: true
})
],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' }
],
entryComponents: COMPONENTS,
exports: COMPONENTS
})
export class AppModule {
ngDoBootstrap(): void { }
}
Everything works fine so far. I can see my angular component inside my angular.js application.
The problem comes when I add the to my main root component
I can see the router-outlet rendering but nothin next to it, eventhough the route matches.
export const routes: Route[] = [
{ path: 'dashboard', component: TestComponent }
];
When I point my browser to /#/dashboard this is the router tracing that I see:
And the test component just doesn't render.
I need some help, can't think of anything else to try.
First of all: if you want to go hybrid and start moving parts of ng1 to ngx, you need to bootstrap your ng1 app from ngx as you did, but not by downgrading:
platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
(<any>platformRef.instance).upgrade.bootstrap(document.body, ['nameOfNg1App']);
});
Than you should provide the entry point for ui-router within your app.component.html:
<div ui-view></div>
You also need to provide an url handling strategy to tell angular, which routes to handle. I had an AppRoutingModule, which was imported by the AppModule. And this one provided the handler:
#NgModule({
imports : [
RouterModule.forRoot(routes, {useHash: true})
],
exports : [
RouterModule
],
// provide custom UrlHandlingStrategy to separate AngularJs from Angular routes
providers: [
{
provide : UrlHandlingStrategy,
useClass: Ng1Ng2UrlHandlingStrategy
}
]
})
export class AppRoutingModule {
}
And the Handling strategy, I used path prefixes to separate ng1 from ngx routes, but you can choose a simpler separation if needed:
import { UrlHandlingStrategy } from '#angular/router';
export class Ng1Ng2UrlHandlingStrategy implements UrlHandlingStrategy {
private ng1Urls: string[];
constructor() {
this.ng1Urls = [
'prefix1',
];
}
shouldProcessUrl(url) {
url = url.toString();
return this.ng1Urls.findIndex(ng1Url => new RegExp(`^\/${ng1Url}([|\?|\/](.*)){0,1}$`).test(url)) === -1;
}
extract(url) {
return url;
}
merge(url, whole) {
return url;
}
}
Oh, and for some reasons I had to stick to # URLs, while running hybrid.
With this setup you start an ngx app, that has a container that runs the ui-router. Within your ng1 app you can then use downgraded ngx components and services.
To have ngx-routes and ng1 routes in parallel, your (ngx) app.component.html consists of
<div ui-view></div>
<router-outlet></router-outlet>
You find more details of this strategy here: https://blog.nrwl.io/upgrading-angular-applications-upgrade-shell-4d4f4a7e7f7b
The solution involved:
Getting rid of UrlHandlingStrategy completely
Disabling initialNavigation
Creating empty routes in both route configurations
Injecting the ng2 router in the ng1 application and adding the following logic
angular.module('app').config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$stateProvider.state('ng2', { });
$urlRouterProvider.otherwise(($injector, $location) => {
const $state = $injector.get('$state');
const ng2Routes = ['dashboard'];
const ng2Router = $injector.get('ng2Router');
const url = $location.url();
if (ng2Routes.some(feature => url.startsWith('/' + feature))) {
$state.go('ng2');
ng2Router.navigate([url]);
} else {
$state.go('messages');
}
});
}]);

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.

Resources