Using Angular NgUpgrade to inject an AngularJS service into an Angular service results in an error message stating: Unhandled Promise rejection: Cannot read property 'get' of undefined; Zone:

I have noticed several similar issues on this platform, but none of the solutions seem to work for me. My understanding is that because our Ng2App is bootstrapped first, it does not have a reference to $injector yet. Consequently, when I attempt to use it in my provider declaration (deps: ['$injector']), it throws an error.

What is extremely peculiar is that I can successfully utilize this service in an Angular COMPONENT, but encounter difficulties when trying to use it in an Angular SERVICE.

Here is the relevant code snippet:

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

 import './ng2app.module';

In the ng2app.module.ts file:

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 <--------------
  }
}

Is anyone familiar with why this issue occurs and how it can be resolved?

A similar question has been asked before, but the suggested solutions did not work for me.

AngularJS version: 1.5.8
Angular/core etc version: 4.2.4

I have also raised this issue on Github in the Angular repository for further reference.

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
   ... (remaining stack trace details are omitted for brevity)

Answer №1

It appears to be a timming issue with the placement of @NgModule({ providers: [] }) and resolving upgrade.bootstrap.

The $injector is required here, but was not injected when requested.

According to the documentation, you should utilize 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 Regrettably, despite following the exact guidance from angular docs, the suggested solution did not work. The initial answer provided was:

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

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

Which seemed closer to a resolution than the current one. However, it turned out that TypeScript was having issues with the syntax.

Update:

We were preoccupied with the useFactory aspect, overlooking the simple fix of adding forwardRef to the service.

@Injectable()
export class AnAngularService{
  constructor(@Inject(forwardRef(() => 'UserService')) private userService: UserService
  ){}
}

Answer №2

After some experimentation, I managed to find a workaround that might not be the most elegant solution, but it does get the job done. However, I believe there must be a more efficient approach out there. Therefore, I won't mark this as resolved, and whoever comes up with a smoother solution will still receive 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(){}
}

^ The trick here is to return a promise and utilize setTimeout to wait for the next tick before resolving the AngularJS injector.

Within 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();
    });
    }
}

As for 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, indeed, this method works, even though it can be considered somewhat of a hack. There must be a cleaner way to achieve the same result. Any suggestions on how to improve this process?

Answer №3

To work around this issue, I decided to utilize the Angular Core Injector class in order to access the upgraded AngularJs service as needed, rather than directly injecting it into the constructor.

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

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

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

    // Accessing myAngularJsUpgradedService now
  }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

trouble encountered when attempting to integrate typeahead functionality in AngularJS using jQuery

Greetings! I am brand new to using AngularJS and currently exploring the implementation of typeahead functionality. I decided to utilize an existing library by including the following script: <script src="lib/xyz/typeahead.bundle.js"></script> ...

Can you guide me on how to access an Angular route using a URL that includes query parameters?

Within my current development project, I have implemented a user profile route that dynamically navigates based on the user's _id. This means that when a user accesses the page, their _id is stored in localStorage and then used to query MongoDB for th ...

The declaration of module 'DynamicTestModule' contains an unexpected value 'StateService'

In my Angular 5 project, I am using UI Router. While running tests for the footer component, I encountered the following error: Failed: Unexpected value 'StateService' declared by the module 'DynamicTestModule'. Please add a @Pipe/@Dir ...

Passing a service into a directive results in an undefined value

Can someone help me understand why a service I am injecting into a directive is returning undefined in certain instances? If you would like to take a look at the code, here is a plunker link: https://plnkr.co/edit/H2x2z8ZW083NndFhiBvF?p=preview var app = ...

Issues with ng-model disappearing while using ng-include (AngularJS)

Currently working on an Android app with Ionic and AngularJS. Using ng-include to include HTML content in the app's pages. checkbox.html: <ul> <li ng-repeat="opt in $parent.checkboxOptions"> <h4> ...

Learn how to create a clickable div that redirects to a URL in Angular JS

Forgive me if this question is basic, but as I delve into learning Angular JS, I encountered an issue. My goal is to create a clickable div that redirects to another page when clicked, but my current implementation isn't working... ...

Applying a setvalidator to a FormControl doesn't automatically mark the form as invalid

HTML code <div> <label for="" >No additional information flag:</label> <rca-checkbox formControlName="noAdditionalInfoCheckbox" (checkboxChecked)="onCheckboxChecked($event)"></rca-chec ...

Detecting Unflushed Requests in Jasmine and AngularJS

I'm encountering some issues passing certain tests after implementing $httpBackend.verifyNoOustandingRequest(). Interestingly, excluding this from my afterEach function allows the tests to pass successfully. However, including it causes all tests to ...

Is it possible to use TypeScript or Angular to disable or remove arrow key navigation from a PrimeNG Table programmatically?

Is there a way to programmatically prevent left and right arrow key navigation in a PrimeNG Table with cell editing, without the need to modify the Table component source code? You can check out an example here: Angular Primeng Tableedit Demo code. I mana ...

Add CSS styling to a particular div element when a button is clicked

I have been working on a new feature that involves highlighting a div in a specific color and changing the mouse pointer to a hand cursor. I successfully achieved this by adding a CSS class: .changePointer:hover { cursor: pointer; background-color ...

Awesomium crashes when attempting to open a website containing an ionic2 application

I created a new ionic2 tabs app using the following commands: ionic start --v2 ionic2.blank tabs ionic serve --nobrowser After that, I copied the www folder to , and it displayed correctly in Windows 7 Chrome browser version 56.x. However, when I tried ...

Tips for styling your Angular Material Card on mobile devices

Currently, I am very happy with my desktop layout which looks like this: https://i.stack.imgur.com/tG0pw.png However, when it comes to the mobile version of my site, here is what I have so far: https://i.stack.imgur.com/KD1hh.jpg While I do like the ho ...

Angular does not delay for promises to settle

Issue I am facing is related to Angular not waiting for promises to be resolved. The console inspection reveals that the provider and skills objects are not retrieved before the promises are returned. I have included the key parts of the code below. The s ...

Error message: In my router module, Angular's 'Subject' is not subscribed to

Currently, I am utilizing a canActivateFn guard in my application where I am subscribing to a Subject. This particular Subject was instantiated in a separate service, and I'm perplexed as to why the subscription does not seem to trigger (the callback ...

Importing a file using its absolute path in JavaScript

Within the dependencies directory, there exists a module named foo: import foo from '../dependencies/foo'; // This import statement works as intended The challenge arises when attempting to import from a different path due to deployment in an AW ...

Exploring Angular 2 with Visual Studio 2015 Update 1 in the context of Type Script Configuration

After spending the last week attempting to set up and launch a simple project, I am using the following configuration: Angular 2, Visual Studio 2015 update 1, TypeScript Configuration In the root of my project, I have a tsconfig.Json file with the follow ...

TypeScript's robustly-typed rest parameters

Is there a way to properly define dynamic strongly typed rest parameters using TypeScript 3.2? Let's consider the following scenario: function execute<T, Params extends ICommandParametersMapping, Command extends keyof Params, Args extends Params[C ...

Exploring Angular 5: Injecting a Service in Unconventional Places

I am attempting to utilize a service outside of a component. My main objective is to use my service within a function wrapped in a data object that I would then pass to my router for use by my Breadcrumb later on. Here is an example of what I envision: ...

Type the query into the search bar on the website, hit the submit button, and receive the search results

Is there a way to dynamically pass any query string (from any oracle table, not hardcoded) from a webpage form/field to the database and have the webpage display a table/grid of the results without predefining columns or table names? Current examples I&apo ...

Having trouble with browser freezing when using array push in Angular 4? Looking for a solution to fix this issue?

In my logsCompoment.ts file, there is an array called logs where I store new log entries and display them on the HTML page using ngFor directive. I achieve this by handling incoming data in the following manner: this.socket.on('newline', (data) ...