ui-router / angular-hybrid

Upgrade an ng1 UI-Router app to a ng1+ng2 hybrid using ng-upgrade
MIT License
167 stars 75 forks source link

Version 12 causes "Unknown provider" on Angular states #490

Closed dpraul closed 3 years ago

dpraul commented 3 years ago

Hi there! We're on Angular 10.2.4 and are trying to upgrade to @uirouter/angular-hybrid 12.0.0.

All the states registered in AngularJS work perfectly, but Angular states display nothing and throw an error like so:

Error: [$injector:unpr] Unknown provider: function WidgetDemoComponent() {
    }DirectiveProvider <- function WidgetDemoComponent() {
    }Directive
https://errors.angularjs.org/1.8.2/$injector/unpr?p0=function%20WidgetDemoComponent()%20%7B%0A%20%20%20%20%7DDirectiveProvider%20%3C-%20function%20WidgetDemoComponent()%20%7B%0A%20%20%20%20%7DDirective
    at angular.js:138
    at angular.js:4991
    at Object.getService [as get] (angular.js:5151)
    at angular.js:4996
    at Object.getService [as get] (angular.js:5151)
    at getComponentBindings (templateFactory.ts:211)
    at TemplateFactory.makeComponentTemplate (templateFactory.ts:203)
    at Ng1ViewConfig.getTemplate (views.ts:137)
    at Object.<anonymous> (viewDirective.ts:393)
    at angular.js:1391 "<ui-view data-ng-animate="1">"

Where WidgetDemoComponent is the component assigned in a state definition like so:

export const states: NgHybridStateDeclaration[] = [
  {
    name: 'widget2-demo',
    url: '/widget2-demo',
    component: WidgetDemoComponent,
  },
];

Any help is appreciated!

christopherthielen commented 3 years ago

It looks like the view is being loaded by AngularJS not Angular v10. I'd make sure that the angular-hybrid code is getting invoked and that the state decorator is being applied to your states. The views in the state definition (i.e.: angular.element($0).injector().get('$uiRouter').stateRegistry.get('widget2-demo').$$state().views ) should have $type: 'ng1-to-ng2'. If they do not, you can try to hard code the $type on the state declaration and see if there's any difference in behavior:

  {
    name: 'widget2-demo',
    url: '/widget2-demo',
    $type: 'ng1-to-ng2',
    component: WidgetDemoComponent,
  },

If there is a different behavior observed, my guess would be that the boostrapping order for modules changed. The hybrid module must be loaded before any modules which register states (the hybrid code is supposed to make sure this is the case).

If you can reproduce the problem, then I can probably help get to the bottom of it.

FYI, the actual code in @uirouter/angular-hybrid that bootstraps Angular/AngularJS hasn't changed much in years. I updated the sample app is to angular 11 and the latest angular-hybrid and it seems OK.

dpraul commented 3 years ago

So, I've done a few things, and I'm still unable to get it to work:

  1. I updated our app to Angular 11 - same error still occurring.
  2. I tried hard-coding the $type - same error. When I run the snippet you gave, I get an object roughly shaped like:
    views = {
    $default: {
    $uiViewName: '$default',
    $name: '$default',
    $type: 'ng1',
    resolveAs: '$resolve',
    component: WidgetDemoComponent,
    $context: {
      ...
      name: 'widget2-demo',
      self: {
        name: 'widget2-demo',
        url: '/widget2-demo',
        $type: 'ng1-to-ng2',
        component: WidgetDemoComponent,
      }
    }
    }
    }
  3. I reorganized the app bootstrapping to match the sample app repo and - you guessed it - still the same error. Only real difference now in the structure of my app vs the sample app is that I'm loading a root state synchronously, and in the sample app it's a nested state loaded asynchronously. No idea if that would be relevant to this issue, though.

The hybrid module must be loaded before any modules which register states (the hybrid code is supposed to make sure this is the case).

By "hybrid module" do you mean @uirouter/angular-hybrid, or do you mean the hybrid Angular/AngularJS app? My app right now is a single Angular module using NgUpgrade to bootstrap a single AngularJS module - could it be an issue that the routes aren't broken out?

christopherthielen commented 3 years ago

By "hybrid module" do you mean @uirouter/angular-hybrid,

yes

could it be an issue that the routes aren't broken out?

If I understand your comment correctly, no I don't think that's the problem.

Can you try running this command to verify that you haven't conflicting versions of the uirouter libraries?

npm ls @uirouter/core @uirouter/angular @uirouter/angularjs @uirouter/angular-hybrid

You should see something very similar to this and no libraries with differing versions (i.e., you should not see two different versions of @uirouter/core):

sample-app-angular-hybrid@1.0.2 /Users/cthielen/projects/uirouter/sample-app-angular-hybrid
├── @uirouter/angular@8.0.0
├── @uirouter/angular-hybrid@12.0.0
├─┬ @uirouter/angularjs@1.0.29
│ └── @uirouter/core@6.0.7  deduped
└── @uirouter/core@6.0.7
dpraul commented 3 years ago

I get identical output to you:

apollo@1.1.35 /home/dylan/projects/fts/website
├── @uirouter/angular@8.0.0
├── @uirouter/angular-hybrid@12.0.0
├─┬ @uirouter/angularjs@1.0.29
│ └── @uirouter/core@6.0.7  deduped
└── @uirouter/core@6.0.7
christopherthielen commented 3 years ago

@dpraul can you provide some way of reproducing this? Alternatively, can we screenshare a debug session (I have 15 minutes right now)?

dpraul commented 3 years ago

Kind of you to offer to screenshare - if you think that would be useful, I am happy to give it a shot. Otherwise I might try pulling down the sample app and trying to reproduce the issue

christopherthielen commented 3 years ago

@dpraul join my meet

dpraul commented 3 years ago

Just to record, Chris was kind enough to spend some time stepping through the issue on a screen-share with me, here's what we found:

Part of our webpack config is babel-plugin-angularjs-annotate. We are also transpiling Angular modules - including ui-router - down with babel from ES2015 to ES5. The result is a part of the ui-router bundle was getting automatic annotations where it shouldn't, which was causing the Angular state to be recognized as an AngularJS state.

Tomorrow I'll be taking that plugin out and seeing if I can make any more progress here.

dpraul commented 3 years ago

Good news is I'm no longer getting the "Unknown provider" issue. I temporarily disabled all babel transpilation of node_modules and the issue went away. It was definitely babel-plugin-angularjs-annotate causing that - thanks for all your help.

I'm sadly still banging away at this one, though, as now I'm getting infinite digest cycle loops and Zone.js is complaining. Going to do more investigation there to see if there are any other inconsistencies I need to address.

That said, this issue is solved, so I suppose this can be closed and I'll make a new issue if I can't resolve the infinite loops.

dpraul commented 3 years ago

I've finally fixed it! The fix probably won't be useful to anyone else, but in case it is, it basically boiled down to an issue with AngularJS using $q for Promises instead of the Promise global.

In our app, we had a blocking Promise in AngularJS that was using $q, since $q hooks into the AngularJS $digest cycle. Because of that last fact, no $q promises can resolve until AngularJS has loaded. This meant our AngularJS routes worked fine, but in the Angular routes something fishy was happening with ZoneAwarePromise and $q that was causing an infinite loop.

Thanks again for all the help here. Happy to finally have these routes working correctly.

christopherthielen commented 3 years ago

happy to help, glad you got it all figured out!

fourgates commented 1 year ago

I ran into this issue too. Issue #55 helped me resolve the issue by excluding ui-router from being transcompiled.

exclude: resource => /@uirouter/.test(resource)

@christopherthielen ng-annotate and ng-annotate-loader are widely used with hybrid applications. Could you add a reference or note to that issue on the README?

Not going to lie, I spent a few days trying to get this to work. Today I was actually going to create a new ticket to document our shortcomings and came across this issue.

I know it's my own fault for not triaging the issues sooner. But I would like to try and save the next struggling developer some time and frustration.