Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue running protractor #39

Closed
MrFrankel opened this issue May 23, 2017 · 7 comments
Closed

Issue running protractor #39

MrFrankel opened this issue May 23, 2017 · 7 comments

Comments

@MrFrankel
Copy link

MrFrankel commented May 23, 2017

I seem to be getting an error while running protractor with angular-hybrid.
Error: Uncaught (in promise): TypeError: Cannot read property \'get\' of undefined

It seems to be coming from:
const url: UrlService = injector.get(UrlService);
where injector is undefined for some reason.

Running browser.ignoreSynchronization = true solves it but breaks our tests.

Any idea why this is?

@MrFrankel
Copy link
Author

Ok, I managed to wrap the bootstrapping code with browser.ignoreSynchronization = true and set it to false when the test starts.

Its a workaround but it works

@jonrimmer
Copy link
Contributor

I'm seeing the same issue.

@jonrimmer
Copy link
Contributor

Actually, I think my issue may have been something different. I have raised it here: angular/protractor#4327

@jonrimmer
Copy link
Contributor

OK, I think these were the same issue, and I have a solution.

The problem is that AngularJS supports "deferred bootstrap". This lets tools like Protractor set a flag on window.name that causes AngularJS to defer finishing bootstrap until something calls angular.resumeBootstrap(extraModules). This allows these tools to insert their own debug modules into any AngularJS app without that app being aware of it.

Now, consider the recommended UI Router hybrid bootstrapping code:

platformBrowserDynamic().bootstrapModule(SampleAppModule).then(platformRef => {
  const injector: Injector = platformRef.injector;
  const upgrade = injector.get(UpgradeModule) as UpgradeModule;

  upgrade.bootstrap(document.body, ['demo']);

  const url: UrlService = injector.get(UrlService);
  url.listen();
  url.sync();
});

UrlService has, indirectly, a dependency on $injector, the AngularJS injector. Therefore, $injector must be available to Angular before we try to get it. Angular's UpgradeModule installs an AngularJS module that captures the $injector instance and provides it to Angular's DI system. Under normal circumstances, this module would be initialised immediately as part of the upgrade.bootstrap() call.

However, because the AngularJS bootstrap is deferred, the call to upgrade.bootstrap() will not cause bootstrapping to happen immediately. It will only happen at some indefinite future point, when Protractor (or whatever) calls angular.resumeBootstrap(). This means that trying to get UrlRouter straight afterward will fail, because the $injector dependency has not been provided to Angular by the UpgradeModule yet.

My solution is to move the UrlService initialisation into a run block of my app's AngularJS module. This ensures it will be run only when both the Angular and AngularJS parts of the hybrid app are ready. E.g.

myAngularJSModule.run(['$$angularInjector', ($$angularInjector) => {
  const url: UrlService = $$angularInjector.get(UrlService);
  url.listen();
  url.sync();
}]);

$$angularInjector is the Angular injector, which UpgradeModule makes available to the AngularJS DI system — the opposite of $injector basically.

And now I'm going to lie down in a dark room for a while until my head stops hurting.

@MrFrankel
Copy link
Author

@jonrimmer Wow! gr8 work... when you get out of the dark room give yourself a pat on the back!

@justtal
Copy link

justtal commented Jun 27, 2017

@jonrimmer You rock! Thanks!

@AlexSwensen
Copy link

Ran through this old answer as i was working on this same problem.

@artaommahe wrote a super helpful code snippet

I modified it to the following:

angular.element(document).ready(() => {
  deferAndSyncUiRouter(angularJSAppModule); // angularjs module as a variable
  platformBrowserDynamic()
    // Manually bootstrap the Angular app
    .bootstrapModule(AppModule) // Angular module imported from app.module.ts
    .then(platformRef => bootstrapWithUiRouter(platformRef, angularJSAppModule));

});

export function deferAndSyncUiRouter(angularjsModule: ng.IModule): void {
  angularjsModule
    .config([ "$urlServiceProvider", ($urlService: UrlService) => $urlService.deferIntercept()])
    .run(["$$angularInjector", $$angularInjector => {
      const url: UrlService = getUIRouter($$angularInjector).urlService;
      url.listen();
      url.sync();
    }]);
}

export function bootstrapWithUiRouter(platformRef: NgModuleRef<any>, angularjsModule: ng.IModule): void {
  const injector = platformRef.injector;
  const upgradeModule = injector.get(UpgradeModule);

  upgradeModule.bootstrap(document.body, [ angularjsModule.name ]);
}

That got everything working for me.

Edit: Also you all may find these imports helpful for the top of your angularjs module file.

import * as angular from 'angular';
import {NgModuleRef} from "@angular/core";
import {getUIRouter} from '@uirouter/angular-hybrid';
import {UrlService} from '@uirouter/angular-hybrid/node_modules/@uirouter/angularjs'
import {UpgradeModule} from "@angular/upgrade/static";
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {AppModule} from "./app.module";

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

No branches or pull requests

4 participants