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

"Jest test.each is throwing errors due to improper data types

Currently, I am utilizing Jest#test.each to execute some unit tests. Below is the code snippet: const invalidTestCases = [ [null, TypeError], [undefined, TypeError], [false, TypeError], [true, TypeError], ]; describe('normalizeNames', ...

Tips for retrieving a server-set cookie in your Angular 2 application, and guidelines for including the same cookie in requests made by your Angular 2 application

Requirement: Our application should be able to support the same user opening our web application as a separate session. The issue is not about how to use cookies in Angular 2, but rather how the server can retrieve cookies from the HTTPServletRequest obje ...

The ng-controller directive fails to function on the content of Kendo tabstrip tabs

My ng-controller is not functioning properly for the kendo tabstrip tab content. Could you please review my code below? <!--tabstripCtrl.js--> angular.module('tabstripApp',[]); var app = angular.module('tabstripApp'); app.con ...

Introduction to Angular initial rendering

I have a straightforward question, but unfortunately I am unsure how to search for it. As my initial page loads, I want Angular to automatically display the first content in <div ng-view>. Subsequently, using $routeProvider takes care of reloading t ...

Is it possible to configure npm to publish to an organization different from the one automatically detected from package.json?

We are looking to implement a process in our open source project where all Pull Requests will be published to npm using CI/CD. To reduce the potential for supply chain attacks, we aim to deploy to a separate organization. Can this be achieved without makin ...

Is it possible to utilize Webpack 5's ChunkGroup API with several entries?

I am encountering an error message when attempting to upgrade from Webpack 4 to Webpack 5. The error states: Module.entryModule: Multiple entry modules are not supported by the deprecated API (Use the new ChunkGroup API) I have searched for information o ...

Transpiler failed to load

My Angular application running on Node has recently encountered a problem with the transpiler. I have been trying to load mmmagic to evaluate uploaded files, but encountered difficulties in the process. While attempting to update NPM packages, I gave up on ...

Guide: Building a Dropdown Form in Angular 2

I have a webpage with an HTML form that includes a button positioned above the form. I am interested in adding functionality to the button so that when it is clicked, a duplicate of the existing form will be added directly beneath it. This will allow for m ...

Tips for accurately implementing the onHoverIn TS type in the React Native Web Pressable component

I'm working with React Native Web and Typescript, and I want to integrate the React Native Web Pressable component into my project. However, I encountered an issue where VSCode is showing errors for React Native Web prop types like onHoverIn. The pro ...

prepend a string to the start of ng-model

I am looking to enhance my ng-model variable by adding a specific string to it. This ng-model is being used for filtering data, specifically for elements that begin with the term "template". By adding this string to the ng-model, I hope to improve my searc ...

Angular Reactive Forms - Adding Values Dynamically

I have encountered an issue while working with a reactive form. I am able to append text or files from the form in order to make an http post request successfully. However, I am unsure about how to properly append values like dates, booleans, or arrays. a ...

Reassigning Key Names and Types Based on Conditions

How can I modify object key names and properties in a way that allows existing keys and properties to remain the same or be modified (remapped)? My current approach does not properly handle mixed cases: export const FUNC_ENDING_HINT = "$func" as const; ty ...

Using Ionic 2 to fetch JSON data and display it using ngFor

My script.php generates a JSON array with data from mySQL; The next step involves retrieving this JSON array via AJAX; I am looking to dynamically create divs using ngFor, but I'm unsure of how to handle the callback for the JSON array in the Ajax s ...

Error: The iOS 14.2 system is unable to locate the variable "webkit."

Currently, I am developing a web application using AngularJS 1.7. The app runs smoothly on Safari with iOS versions 12, 14.0, and 14.1. However, upon upgrading my iOS to version 14.2/14.3 (tested on both), I encountered the following error: Error: Referen ...

Managing clicks outside of the render function

I'm brand new to using React and I'm currently exploring how to properly manage an event handler outside of the Return() function within a component. If there's a more efficient or conventional way to do this, I'm definitely open to sug ...

SheetJS excel-cell customization

I'm using this example to export a worksheet from https://github.com/SheetJS/js-xlsx/issues/817. How can I apply cell styling such as background color, font size, and adjusting the width of cells to fit the data perfectly? I have looked through the do ...

Issue with NGRX: component does not reflect state changes

Whenever I click on a button, it is supposed to open up a Google map. Upon clicking, I can see Google scripts being inserted, which triggers a callback that sends a message to my reducer. After receiving the message, I can confirm through logging that the ...

Converting dates in JavaScript to the format (d MMMMM yyyy HH:mm am) without using moment.js

Looking to convert the date "2020-02-07T16:13:38.22" to the format "d MMMMM yyyy HH:mm a" without relying on moment.js. Here is one method being utilized: const options = { day: "numeric", month: "long", year: "numeric", } var date1 = new Date ...

How can I detect the shift key press when an array key is pressed in Angular 2?

I have a collection of items that I want to implement file traversal behavior for, similar to a file explorer. This means that after selecting an item, if you hold down the shift key and press the down arrow, those items should also be selected. Below is ...

Angular - delay execution until the variable has a value

When the ngOnInit() function is called, the first line of code retrieves a value from local storage which is then used to filter data from the database. Both actions happen simultaneously, resulting in an issue where I don't receive the expected resu ...