Angular NgUpgrade inject AngularJS service into Angular service; getting: Unhandled Promise rejection: Cannot read property 'get' of undefined ; Zone:
Asked Answered
B

3

7

I'm seeing a lot of similar problems on here, but have yet to find a solution that works. What I THINK is happening is that, because our Ng2App is bootstrapped first, it doesnt have a reference to $injector yet, so when I try to use it in my provider declaration (deps: ['$injector']), it doesn't exist.

What's INSANELY weird is that I can use this service in an Angular COMPONENT but for some reason cant use it in an Angular SERVICE.

app.js

import UserService from './user.service';
angular.module('app', [])
  .service('UserService', UserService)
  .config(/* config */)
  .run(/* run */);

 import './ng2app.module';

ng2app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { UpgradeModule } from '@angular/upgrade/static';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
@NgModule({
  imports: [
    BrowserModule,
    UpgradeModule,
  ], 
  declarations: [],
  entryComponents: [],
  providers: [
    // angularJS service:
    { 
     provide: 'UserService',
     useFactory: (i: any) => i.get('UserService'), // <---- this is the line all the errors point to.
     deps: ['$injector']
    },
  ]
})
export default class Ng2AppModule {
  constructor(){}
}


platformBrowserDynamic()
  .bootstrapModule(Ng2AppModule)
  .then(platformRef => {
    const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
    upgrade.bootstrap(document.documentElement, ['app'], {strictDi: true});
});

Later... in a service (fails):

import {Injectable, Inject} from "@angular/core";
import UserService from 'app/login/user.service';

@Injectable()
export class AnAngularService{
  constructor(
    // causes the error if I uncomment it wtf: <--------------
    // @Inject('UserService') private userService: UserService
  ){}
}

Later... in a component (works properly):

import { Component } from '@angular/core';
import {Inject} from "@angular/core";
import UserService from 'app/login/user.service';
import template from 'tmpl.html';

@Component({
  selector: 'an-angular-component',
  template,
})
export class AnAngularComponent{
  constructor(

    @Inject('UserService') private userService: UserService
  ){
    console.log(userService) // works just fine. wtf <--------------
  }
}

Does anyone know why this is happening and how to fix it?

This question is almost exactly the same thing but for some reason it didnt work

AngularJS version: 1.5.8
Angular/core etc version: 4.2.4

Here's a link to the Github issue I opened in the Angular repo

StackTrace:

zone.js:522 Unhandled Promise rejection: Cannot read property 'get' of undefined ; Zone: <root> ; Task: Promise.then ; Value: TypeError: Cannot read property 'get' of undefined
    at useFactory (ng2app.module.ts:114)
    at _callFactory (core.es5.js:9604)
    at _createProviderInstance$1 (core.es5.js:9547)
    at initNgModule (core.es5.js:9498)
    at new NgModuleRef_ (core.es5.js:10606)
    at createNgModuleRef (core.es5.js:10590)
    at Object.debugCreateNgModuleRef [as createNgModuleRef] (core.es5.js:12874)
    at NgModuleFactory_.create (core.es5.js:13869)
    at core.es5.js:4556
    at ZoneDelegate.invoke (zone.js:334)
    at Object.onInvoke (core.es5.js:3933)
    at ZoneDelegate.invoke (zone.js:333)
    at Zone.run (zone.js:126)
    at NgZone.run (core.es5.js:3801)
    at PlatformRef_._bootstrapModuleFactoryWithZone (core.es5.js:4554)
    at core.es5.js:4596
    at ZoneDelegate.invoke (zone.js:334)
    at Zone.run (zone.js:126)
    at zone.js:713
    at ZoneDelegate.invokeTask (zone.js:367)
    at Zone.runTask (zone.js:166)
    at drainMicroTaskQueue (zone.js:546)
    at <anonymous> TypeError: Cannot read property 'get' of undefined
    at useFactory (http://localhost:9000/app.bundle.js:4404:52)
    at _callFactory (http://localhost:9000/vendor.bundle.js:10600:26)
    at _createProviderInstance$1 (http://localhost:9000/vendor.bundle.js:10543:26)
    at initNgModule (http://localhost:9000/vendor.bundle.js:10494:13)
    at new NgModuleRef_ (http://localhost:9000/vendor.bundle.js:11602:9)
    at createNgModuleRef (http://localhost:9000/vendor.bundle.js:11586:12)
    at Object.debugCreateNgModuleRef [as createNgModuleRef] (http://localhost:9000/vendor.bundle.js:13870:12)
    at NgModuleFactory_.create (http://localhost:9000/vendor.bundle.js:14865:25)
    at http://localhost:9000/vendor.bundle.js:5552:61
    at ZoneDelegate.invoke (http://localhost:9000/vendor.bundle.js:289131:26)
    at Object.onInvoke (http://localhost:9000/vendor.bundle.js:4929:37)
    at ZoneDelegate.invoke (http://localhost:9000/vendor.bundle.js:289130:32)
    at Zone.run (http://localhost:9000/vendor.bundle.js:288923:43)
    at NgZone.run (http://localhost:9000/vendor.bundle.js:4797:62)
    at PlatformRef_._bootstrapModuleFactoryWithZone (http://localhost:9000/vendor.bundle.js:5550:23)
    at http://localhost:9000/vendor.bundle.js:5592:59
    at ZoneDelegate.invoke (http://localhost:9000/vendor.bundle.js:289131:26)
    at Zone.run (http://localhost:9000/vendor.bundle.js:288923:43)
    at http://localhost:9000/vendor.bundle.js:289510:57
    at ZoneDelegate.invokeTask (http://localhost:9000/vendor.bundle.js:289164:31)
    at Zone.runTask (http://localhost:9000/vendor.bundle.js:288963:47)
    at drainMicroTaskQueue (http://localhost:9000/vendor.bundle.js:289343:35)
    at <anonymous>
consoleError @ zone.js:522
handleUnhandledRejection @ zone.js:527
_loop_1 @ zone.js:562
drainMicroTaskQueue @ zone.js:566
Promise resolved (async)
scheduleQueueDrain @ zone.js:505
scheduleMicroTask @ zone.js:513
ZoneDelegate.scheduleTask @ zone.js:356
Zone.scheduleTask @ zone.js:196
Zone.scheduleMicroTask @ zone.js:207
scheduleResolveOrReject @ zone.js:711
ZoneAwarePromise.then @ zone.js:800
PlatformRef_._bootstrapModuleWithZone @ core.es5.js:4596
PlatformRef_.bootstrapModule @ core.es5.js:4581
(anonymous) @ ng2app.module.ts:140
__webpack_require__ @ bootstrap 2f644bad14cb0bb324ab:691
fn @ bootstrap 2f644bad14cb0bb324ab:110
(anonymous) @ app.js:116
__webpack_require__ @ bootstrap 2f644bad14cb0bb324ab:691
fn @ bootstrap 2f644bad14cb0bb324ab:110
(anonymous) @ util (ignored):1
__webpack_require__ @ bootstrap 2f644bad14cb0bb324ab:691
webpackJsonpCallback @ bootstrap 2f644bad14cb0bb324ab:23
(anonymous) @ app.bundle.js:1
Bagger answered 11/8, 2017 at 21:44 Comment(3)
Can you add the error?Spumescent
I just added the stacktrace.Bagger
useFactory: (i: any) => i.get('UserService') Shouldn't i be typed? I got the same error many times when an Observable was expected but I gave an object.Eyebrow
S
4

It seems to be an issue with the timming of the @NgModule({ providers: [] }) and the upgrade.bootstrap resolution.

Here you need the $injector but it wasn't injected at the moment that it was requested.

In the docs it says that you should use the ngDoBootstrap hook.

export function userServiceFactory(i: any) {
  return i.get('UserService');
}

export const userServiceProvider = {
  provide: 'UserService',
  useFactory: userServiceFactory,
  deps: ['$injector']
};

import { BrowserModule } from '@angular/platform-browser';
import { UpgradeModule } from '@angular/upgrade/static';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
@NgModule({
  imports: [
    BrowserModule,
    UpgradeModule,
  ], 
  declarations: [],
  entryComponents: [],
  providers: [
     userServiceProvider
  ]
}) 


export default class Ng2AppModule {

   constructor(private upgrade: UpgradeModule) { }

   ngDoBootstrap() {
     this.upgrade.bootstrap(document.body, ['app'], { strictDi: true });
   }
}

platformBrowserDynamic().bootstrapModule(Ng2AppModule);

edited by andrew luhring for posterity Unfortunately that didn't work even though that's exactly what's written in the angular docs. The original answer here was:

import { forwardRef } from '@angular/core';

useFactory: (forwardRef(() => '$injector')i: any) => i.get('UserService')

and that seemed closer to an answer than this. This doesn't work either-- but that seems to be because TypeScript doesn't think that the syntax is right.

Update:

We were so obsessed with the useFactory that we didn't see that the fix was just to add the forwardRef to the service.

@Injectable()
export class AnAngularService{
  constructor(@Inject(forwardRef(() => 'UserService')) private userService: UserService
  ){}
}
Spumescent answered 14/8, 2017 at 17:48 Comment(10)
I'm getting [at-loader] ./src/app/ng2app.module.ts:113:30 TS1005: '=' expected. [at-loader] ./src/app/ng2app.module.ts:113:49 TS1005: ',' expected. it's complaining about the opening parenthesis in forwardRef( <---- and the closing parenthesis after any) <---Bagger
I'll update the answer, this won't work in a factory.Spumescent
Nope. That's basically the exact thing the angular docs say to do and that's what causes the Unhandled Promise rejection: Cannot read property 'get' of undefined error. I think you were on to something with forwardRef but I can't figure out how to get it to workBagger
Yeap, forwardRef will give you a timeout until the app can access to $injector. Let me see if I can create a plunker for this issue.Spumescent
You're doing awesome right now by the way. you ruleBagger
Throwing a long shoot, useFactory: ($injector: any) => $injector.get('UserService') with the deps:['$injector'], I cannot create the plunker in the office haha.Spumescent
lol thats the first thing we tried lol. the forwardRef thing is what I think the solution is but i cant get it to workBagger
If you get the forward ref thing to work I think that'll be the solution tbh. I'm going to edit to show your original answer because that seems closer to the solution than what you have now- this is exactly what's in the angular docs and it doesn't work.Bagger
Update! I hope it helps, you can remove the provider from the NgModule, you are adding the service in the bootstrap.Spumescent
wait remove the provider from the NgModule? If I do that i get "no provider found for UserService" when I inject your code. do I do something like @Inject(forwardRef(($i)=>$i.get('UserService')) private userService: UserService ? what you have in your update right now fails with the cannot read get of undefined thing stillBagger
B
3

Ok so I figured out a hack to make it work. it's super gross, but it works. there's got to be a better solution so I'm not marking this as solved and whoever comes up with a better solution still gets the bounty.

import { BrowserModule } from '@angular/platform-browser';
import { UpgradeModule } from '@angular/upgrade/static';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
@NgModule({
  imports: [
    BrowserModule,
    UpgradeModule,
  ], 
  declarations: [],
  entryComponents: [],
  providers: [
    // angularJS service:
    {
      provide: 'UserService',
      useFactory: () => {
        return new Promise((resolve) => {
          setTimeout(function(){
            resolve(angular.element(document)
                .injector().get('UserService'))
          },1);
        })
      },
      deps: []
    },

  ]
})
export default class Ng2AppModule {
  constructor(){}
}

^ return a promise and use setTimeout to wait for the next tick before resolving the angularJS injector.

In your service:

import {Injectable, Inject} from "@angular/core";
import UserService from 'app/login/user.service';

@Injectable()
export class AnAngularService{
  constructor(
    @Inject('UserService') private userService: any,
  ){

    userService.then(function(_userService){
       _userService.doAThing();
    });
    }
}

In your component:

import { Component } from '@angular/core';
import {Inject} from "@angular/core";
import UserService from 'app/login/user.service';
import template from 'tmpl.html';

@Component({
  selector: 'an-angular-component',
  template,
})
export class AnAngularComponent{
  constructor(
    @Inject('UserService') private userService: any,
  ){
    userService.then((us)=>{ console.log(us); })
  }
}

So yeah. This works. But it's a hack. So it's possible. How can I do it in a less hacky way?

Bagger answered 14/8, 2017 at 17:19 Comment(7)
Have you tried with useFactory: (forwardRef(() => '$injector')i: any) => i.get('UserService') ?Spumescent
I have not. I'll try it in a second, while i do that, integrate it as an answer and if it works you'll get the solution :-)Bagger
I'm having trouble getting the syntax correct so it works with typescript. the way you wrote it shows ')' expected. Declaration or statement expected. ...etcBagger
@camaron Still trying to get the forward reference thing working. typescript keeps complaining and I'm not entirely sure how to get it to stop in this instanceBagger
What is the specific error that TypeScript gives you? Also, note that using Injectable() and Inject(token) together is potentially problematic since they both create metadata to resolve the same dependency. In this case, since userService is typed as any the compiler will emit decorator metadata specifying the dependency as Object, which is not what you want.Lovel
@AluanHaddad the specific error with typescript had to do with the syntax of getting forwardRef to work. i never figured out how to do it.Bagger
Sorry, I'm not sure what you mean. There is no syntax that is unique to ForwardRefLovel
V
0

I was able to get around this issue by using the Angular Core Injector class to get the upgraded AngularJs service when needed, instead of injecting it in the constructor.

import { Injector } from '@angular/core';

@Injectable()
class MyAngularService {
  constructor(private injector: Injector) {
    //
  }

  myMethodUsingUpgradedService() {
    const myAngularJsUpgradedService = this.injector('MyAngularJsUpgradedService');

    // myAngularJsUpgradedService is now available
  }
}
Vaso answered 8/3, 2018 at 15:42 Comment(1)
Getting this error: Cannot invoke an expression whose type lacks a call signature. Type 'Injector' has no compatible call signatures.Squalid

© 2022 - 2024 — McMap. All rights reserved.