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
Andrew Luhring
  • 1,762
  • 1
  • 16
  • 37

3 Answers3

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
  ){}
}
dlcardozo
  • 3,953
  • 1
  • 18
  • 22
  • 1
    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) <--- – Andrew Luhring Aug 14 '17 at 18:32
  • 1
    I'll update the answer, this won't work in a factory. – dlcardozo Aug 14 '17 at 18:41
  • 1
    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 work – Andrew Luhring Aug 14 '17 at 20:14
  • 2
    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. – dlcardozo Aug 14 '17 at 20:18
  • 2
    You're doing awesome right now by the way. you rule – Andrew Luhring Aug 14 '17 at 20:48
  • 1
    Throwing a long shoot, `useFactory: ($injector: any) => $injector.get('UserService')` with the `deps:['$injector']`, I cannot create the plunker in the office haha. – dlcardozo Aug 14 '17 at 20:57
  • 1
    lol thats the first thing we tried lol. the forwardRef thing is what I think the solution is but i cant get it to work – Andrew Luhring Aug 14 '17 at 21:07
  • 2
    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. – Andrew Luhring Aug 15 '17 at 16:51
  • 1
    Update! I hope it helps, you can remove the provider from the `NgModule`, you are adding the service in the `bootstrap`. – dlcardozo Aug 15 '17 at 18:27
  • 1
    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 still – Andrew Luhring Aug 16 '17 at 15:23
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?

Andrew Luhring
  • 1,762
  • 1
  • 16
  • 37
  • Have you tried with `useFactory: (forwardRef(() => '$injector')i: any) => i.get('UserService')` ? – dlcardozo Aug 14 '17 at 17:26
  • 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 :-) – Andrew Luhring Aug 14 '17 at 17:40
  • I'm having trouble getting the syntax correct so it works with typescript. the way you wrote it shows ')' expected. Declaration or statement expected. ...etc – Andrew Luhring Aug 14 '17 at 17:47
  • @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 instance – Andrew Luhring Aug 14 '17 at 18:17
  • 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. – Aluan Haddad Aug 18 '17 at 12:42
  • 1
    @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. – Andrew Luhring Sep 07 '17 at 21:58
  • Sorry, I'm not sure what you mean. There is no syntax that is unique to `ForwardRef` – Aluan Haddad Sep 08 '17 at 01:47
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
  }
}
  • Getting this error: Cannot invoke an expression whose type lacks a call signature. Type 'Injector' has no compatible call signatures. – Will Mar 14 '18 at 18:50