Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

NgUpgrade init breaks when run through Protractor #4327

Closed
jonrimmer opened this issue Jun 15, 2017 · 3 comments
Closed

NgUpgrade init breaks when run through Protractor #4327

jonrimmer opened this issue Jun 15, 2017 · 3 comments
Assignees

Comments

@jonrimmer
Copy link

The Angular static NgUpgrade module contains code to provide the AngularJS $injector service to Angular. The ui-router library relies on this to get access to AngularJS dependencies from an Angular context. When a hybrid app is run through Protractor, this breaks, and $injector is not available in an Angular context.

The code responsible for providing $injector to Angular is in upgrade_module.ts. The upgrade adds a new AngularJS module that stashes the $injector instance in a local var, which it then provides to Angular when UpgradeModule is imported into a hybrid app.

For some reason, it seems that when an app is running under Protractor, the upgrade AngularJS module code is not called, which means the copy of $injector it later provides to Angular is undefined.

I have slightly modified the test app from the Protractor source tree to demonstrate the problem:

https://github.com/jonrimmer/protractor-ngupgrade-bug

I have added some code to the update/app/main.ts to populate window.$injector with whatever the Angular injector has stored for '$injector' when the app has finished bootstrapping.

If the example is opened through a normal browser, it can be confirmed that window.$injector is populated with the $injector instance.

However, when the test contained in spec.js is run through Protractor, the value of window.$injector is null.


  • Node Version: v7.90
  • Protractor Version: 5.1.2
  • Angular Version: 1.6 and `4
  • Browser(s): Tested in Chrome and Firefox
  • Operating System and Version macOS 10.12.5
@heathkit
Copy link
Contributor

Thanks for the detailed description and repo. I'm 90% sure the problem is where upgradeModule patches resumeBootstrap. Protractor expects this to return a reference to the injector, which it then assigns to window.$injector. The patched version return null, since it needs to be async.

Protractor needs a new way to get ahold of the injector. I'm not sure I understand why apps are looking for window.$injector though. What if there are multiple Angular apps on a page? Also, in this upgrade app are you using the AngularJS or the Angular version of ui-router?

From within Angular code, the right way to get the AngularJS injector is with Angular's dependency injection. I don't have a great example on hand, but something like this:

// ng.auto.IInjectorService is from the AngularJS typings
export function getOldService(i: ng.auto.IInjectorService) {
  // i is a reference to $injector from AngularJS
  return i.get('oldAngularJSServie');
}

@NgModule({
  providers: [{
    provide: OldService,
    useFactory: getOldService,
    deps: ['$injector']
  }],
})
export class oldServiceModule {
}

@heathkit heathkit self-assigned this Jun 15, 2017
@jonrimmer
Copy link
Author

OK, after a lot more debugging, I don't think this is a Protractor issue. It is a general problem with using the Upgrade module and deferred bootstrapping, which UI Router is triggering when run through Protractor.

The problem is that, when bootstrapping a hybrid app normally, $injector is available to Angular as soon as you've called upgrade.bootstrap(...). But if AngularJS bootstrapping is deferred, then the injector will only become available after something has called angular.resumeBootstrap(), which will happen at some indefinite future time. UI Router's hybrid support expects it to be available immediately.

The solution is to ensure that any Angular component or service that expects $injector to be available is not initialised until after the AngularJS part of the app has finished bootstrapping. I've fixed this for UI Router by adding a run() block to my AngularJS module, into which I inject $$angularInjector (which is the Angular injector made available to AngularJS code — the opposite of $injector), which I then use to access and initialise the UI Router from the AngularJS side of things.

Fun.

@jensbodal
Copy link

jensbodal commented Sep 20, 2017

I'd just like to comment here since this is the most relevant information I found. You cannot use upgraded AngularJS services (and probably components) in an Angular component that is also in the Bootstrap array in AppModule and have it work with protractor.

We were writing an Angular component that we needed to downgrade and use in AngularJS, and we also had our router-outlet defined here since they both needed to be in our site's index.html file. This component is using some upgraded AngularJS services. The solution was to put the code that uses upgraded services/components into a different component and then downgrade it. The downgraded component goes in AppModule's entryComponents and the router-outlet component goes in bootstrap component array.

Our index.html now looks something like this:

<app-router-root></app-router-root>
<downgraded-angular-component></downgraded-angular-component>

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants