ui-router: 1.0: Can't inject $transition$ into component's controller?

Trying to route to component, but the following throws an error: Unknown provider: $transition$Provider <- $transition$

.component('adminApplicants',{
    templateUrl:'admin.applicants.html',
    controller:function($http,$transition$){
    }
})

With legacy .controller(“SomeCtrl”,function($transition$){}), there is no error. Why?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 1
  • Comments: 21 (8 by maintainers)

Most upvoted comments

Yes, this is a limitation of route to component.

$transition$ isn’t a global service, it’s a locally scoped injectable. In old school .controller() style, ui-router actually instantiates the controller using $controller(yourControllerFunction) so we can pass locals to it (such as $transition$). With route-to-component, we no longer instantiate the controller ourselves. Instead, we generate a template string, such as <admin-applications></admin-applications> and let angular do its thing.

You can use component input bindings to address this:

.component('adminApplicants',{
    bindings: { $transition$: '<' },
    templateUrl:'admin.applicants.html',
    controller:function($http,$transition$){
    }
})

Now the generated template looks like this: <admin-applications $transition$="::$resolve.$transition$"></admin-applications>

The $transition$ will be bound on your controller.

Keeping this ticket open because it needs to be added to the docs, here: https://github.com/ui-router/ui-router.github.io/blob/master/_guide/105.ng1-route-to-component.md

To summarize this issue

  • You cannot inject $transition$ into a component controller’s constructor when using route-to-component.
    • The $transition$ object is not a global service
    • The component is constructed by angular itself, so we cannot provide additional DI locals
  • You cannot rename the $transition$ to a different variable on the controller in the component’s bindings.

Workarounds

  • You can bind $transition$ directly
    • .component('foo', { bindings: { $transition$: '<' } })
  • You can globally alias the $transition$ token yourself
    • $transitionsProvider.onBefore({}, trans => trans.addResolvable({ token: "trans", resolveFn: () => trans });
  • You can locally alias the $transition$ token yourself
    • $stateProvider.state({ ... resolve: { trans: '$transition$' } });
  • You can map your component input (blarble) to a specific resolve ($transition$) using ui-router component view bindings.
    • $stateProvider.state({ ... component: 'foo', bindings: { blarble: '$transition$' } });

Because a) injecting into the component is impossible and b) I have no control over renaming the binding and c) there are four workarounds, I am closing this issue as wontfix

This also applies to https://ui-router.github.io/docs/latest/modules/ng1.html#_stateparams where it says to inject $transition$ using $inject, which does not work as I get the same error as mentioned above. Also, the migration guide says to inject $transition$ as well. Or, is there something I’m missing?

@loxy Inject $uiRouterGlobals and use its attribute params [Object] like this:

$uiRouterGlobals.params.myParam

@sebastian-zarzycki-es @shawnlan

Thanks to @frederikprijck for creating the PR which closed this ticket

It was merged, and should (I assume) land in angular 1.6.2

I think you need to use $onInit: https://toddmotto.com/angular-1-6-is-here

It’s a breaking change in 1.6.

@sebastian-zarzycki-es

alias resolve

You can create an alias in a resolve:

  $stateProvider.state({ 
    name: 'home', 
    url: '/home', 
    component: 'home',
    resolve: { 
      homeData: () => 'home data',
      trans: '$transition$'
    },
  });
app.component('home', {
  bindings: { trans: '<', homeData: '<' },
  template: `
  <h1>home state loaded: {{ $ctrl.homeData }}</h1>
  <h1>Transition ID: {{ $ctrl.trans.$id }} </h1>
  <div ui-view></div>
`});

Global alias

Or you can give it a global alias using a transition hook:

  $transitionsProvider.onBefore({}, trans =>
      trans.addResolvable(new Resolvable("flooble", () => trans)));
app.component('foo', {
  bindings: { flooble: '<', fooData: '<' },
  template: `
  <h1>foo state loaded: {{ $ctrl.fooData }}</h1>
  <h1>Transition ID: {{ $ctrl.flooble.$id }}</h1>
`});

plunker: http://plnkr.co/edit/uMM7mshrxN23RdI17VHT?p=preview