AngularJSの組み込みのservice($location等)をAngularへDIする方法

最近、仕事でAngularJSからAngularへのアップグレードをしています。その辺りのことは、ペパボ EC テックカンファレンスでも同僚の@ku00_がトークするので、興味のある方はそちらもどうぞ。

今のプロジェクトでは、ngUpgrade を利用してAngularJSとAngularを共存させる形で、徐々に移行しています。

その際に詰まった所が、調べても中々hitしなかったので、ブログにしておきます。

問題

ngUpgradeを利用して、AngularJsとAngularのハイブリットのアプリケーションを動かす時、Angularコンポーネントの中でAngularJSの組み込みのservice達 ($location, $log等) を使いたいケースがありました。

具体的に自分たちに起きたケースとしては、ボトムアップ的にAngularへの置き換えをしている中で、AngularからAngularJSで定義しているrouteへ遷移したいというものです。AngularJSでは、 $location.path('/newRoute') を呼び出せばいいだけですが、それをAngularでやるにはどうすればいいでしょうか?

解決方法

公式ドキュメントを読むと、AngularJS の依存性を Angular に注入できるようにする という セクションがあります。これをすると、AngularJSの世界からAngularの世界へDIが可能になるようです。

サンプルとして書かれていたものは、組み込みのserviceではなく、自作のserviceの方法でした。

import { HeroesService } from './heroes.service';

@NgModule({
  providers: [
    {
       provide: HeroesService,
       useFactory: ($injector: any) => $injector.get('heros'),
       deps: ['$injector']
    }
  ]
})

DIするときは、普通に型でDI出来ます。

  constructor(heroesService: HeroesService) {}

これで providers に指定しているのは、FactoryProvider と呼ばれるものです。provide には、型やInjectionToken と呼ばれる文字列を指定することが出来て、どのような形でDIするかが、ここの指定によって変わります。classを指定した場合は、そのclassでinjection出来ます。ただし、interfaceを指定することは出来ないため、AngularJSの組み込みserviceを指定する場合は文字列がよさそうです。

useFactory 関数ではDIする実体を決定できて、$injector を使うとAngularJSの世界でDI可能なものを取得できるので、それを利用しているようです。

$location のようなAngularJSなどの組み込みのserviceをDIする例です。

@NgModule({
  providers: [
    {
       provide: '$location',
       useFactory: ($injector: any) => $injector.get('$location'),
       deps: ['$injector']
    }
  ]
})

DIするとき

  constructor(@Inject('$location') $location) {}

このように、provide で文字列を指定した場合は、DIするときに @Inject を利用する必要があります。Angularの providers がやっていることやDIが、なんとなくわかってきたような気がします。