17
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Angularの依存性注入とProviderについて

Last updated at Posted at 2019-12-23

私のインターンシップ先の先輩が以前に
「ServiceはSingletonであることがAngular側で保証されている」
とおっしゃっているのを耳にしたことがあり、今更ながらその言葉の意味についてまとめておこうと思います。

Angular2のDIを知る

本記事は上記の記事を参考にしています。

依存性の注入(DI)

まずはAngularにおける依存性の注入についてです。

Angular2においては「 Providerから提供されているインスタンスを特定の変数にInject(注入)する仕組み 」のことを指します。 Provider と Inject の2つの関係は重要なので頭に入れておきましょう。

ここで、注入するものは「Service」、注入されるものを「Component」として進めていきます。

「Providerから提供されている」というのは、Componentデコレータのprovidersプロパティに配列として格納されているクラスのことです。
例えば、

dependency-injection.component.ts
@Component({
  selector: 'app-dependency-injection',
  templateUrl: './dependency-injection.component.html',
  styleUrls: ['./dependency-injection.component.scss'],
  providers: [DependencyInjectionService]
})
export class DependencyInjectionComponent implements OnInit {

  constructor(
    private di1: DependencyInjectionService,
  ) { }

  ngOnInit() {}
}

このようなComponentがあった時、DependencyInjectionServiceはProviderから提供されています。
そして、Component内のコンストラクタの引数に注入したいクラスの型を指定することで注入を行います。

これはAngular側の決まりで、コンストラクタ内の引数型から、注入すべきServiceをprovidersの中から決定しています。

Serviceの中身はこんな感じです。

dependency-injection.service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class DependencyInjectionService {

  constructor() {    
  }

}

注入する側のクラスにはInjectableデコレータをつけます。必ずつけなければならないというわけではありませんが、つけておいた方が良いです。

#Providerの役目

これでComponent内からServiceのインスタンスを扱えるようになりましたが、そもそもインスタンスの生成を行っているのはProviderです。

providersに渡す値ですが、省略せずに書くとこのようになります。

dependency-injection.component.ts
@Component({
  selector: 'app-dependency-injection',
  templateUrl: './dependency-injection.component.html',
  styleUrls: ['./dependency-injection.component.scss'],
  providers: [
    { 
      provide: DependencyInjectionService,
      useValue: DependencyInjectionService 
    }
  ]
})
export class DependencyInjectionComponent implements OnInit {

  constructor(
    private di1: DependencyInjectionService,
  ) { }

  ngOnInit() {}
}

まずprovideのバリューとして与えているのはDIトークンというものです。このトークンはServiceを特定するためのトークンです。値としてtypeが指定できますが、一般的に注入するServiceの型がそのまま指定されます。
ここでインスタンス化が行われています。

そして、コンストラクタの引数では、DIトークンを使用してServiceのインスタンスを結び付けているということです。
なので例えば、

dependency-injection.component.ts
class Foo {
}

@Component({
  selector: 'app-dependency-injection',
  templateUrl: './dependency-injection.component.html',
  styleUrls: ['./dependency-injection.component.scss'],
  providers: [
    { provide: Foo, useValue: new DependencyInjectionService() }
  ]
})
export class DependencyInjectionComponent implements OnInit {

  constructor(
    private di$: Foo,
  ) { }

  ngOnInit() {
    console.log(this.di$); // DependencyInjectionService {}
  }
}

こんなこともできてしまいます。ですが普通はこんなことはせず、素直にServiceの型をそのままDIトークンとして使います。

次に見ていくのはuseValueですが、この記事の肝となる部分なのでセクションを分けます。

サービスの生成方法について

これまでに見てきたように、Componentに注入する際にServiceのインスタンスかを行うのはproviderの役目でした。さらにproviderではどのようにインスタンスを取り扱うかも定義できます。

その定義方法は以下の4つです。

  • useClass
  • useValue
  • useExisting
  • useFactory

この内useClass、useValueの2つについてのみ詳しく見ていきます。

useValue

まず先ほどのプログラムにあったuseValueからです。これは、DIトークンに対応するオブジェクトのインスタンスをそのまま返します。つまり、この方式で作成したインスタンスは、Component内で複数のプロパティに結び付けられていても同じものとして扱うのです。

まずService側でこのような実装を行います。

dependency-injection.service.ts

import { Injectable } from '@angular/core';

@Injectable()
export class DependencyInjectionService {

  count = 0;

  constructor() {
  }

  increment() {
    this.count += 1;
  }
}

現在の時間をミリ秒で受け取って、プロパティに持たせています。
これを以下のようにしてComponent側から呼び出して見ます。

dependency-injection.component.ts
@Component({
  selector: 'app-dependency-injection',
  template: `{{ di$.count }}`,
  styleUrls: ['./dependency-injection.component.scss'],
  providers: [
    { provide: DependencyInjectionService, useValue: new DependencyInjectionService }
  ]
})
export class DependencyInjectionComponent implements OnInit {

  constructor(
    private di$: DependencyInjectionService
  ) { }

  ngOnInit() {
    this.di$.increment();

}

Componentをこのように作成して、親コンポーネントから3回呼び出してみます。
すると、

スクリーンショット 2019-12-24 0.20.50.png

ブラウザにはこのように表示されます。これは、各Componentに紐づいているServiceインスタンスは同じであるため、Component間で関数呼び出しによる影響が及んでいることが確認できます。

##useClass

ではuseClassの場合についてです。

dependency-injection.component.ts
@Component({
  selector: 'app-dependency-injection',
  template: `{{ di$.count }}`,
  styleUrls: ['./dependency-injection.component.scss'],
  providers: [
    { provide: DependencyInjectionService, useClass: DependencyInjectionService }
  ]
})
export class DependencyInjectionComponent implements OnInit {

  constructor(
    private di$: DependencyInjectionService,
  ) { }

  ngOnInit() {
    this.di$.increment();
  }

}

useClassの場合、providerでクラスが指定されるたびに新しいインスタンスの作成を行います。なので、各Componentに紐づいているインスタンスは全く別のものであり互いに影響を及ぼし合いません。

スクリーンショット 2019-12-24 0.23.32.png

これが大きな違いです。
これを理解すると先輩がおっしゃていたことが理解できます。

#ServiceはSingletonってつまりこういうこと

AngularのServiceをコマンドラインツールから作成すると、デフォルトではこんな感じになります。

typescript:dependency-injection.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DependencyInjectionService {

  constructor() {
  }
}

provideInが肝で、これはルートのモジュール対してprovideしますよ、ということです。なので開発者側でproviderの登録をする必要は本来あまりありません。

ルートでインスタンスを作成しているので下位のComponentはインジェクターツリーの関係からインスタンスを共有します。
これはアプリケーション全体で一つのインスタンスを参照していると言い換えることができるので、それはつまりSingletonだよね、ということなのだと思います。

#まとめ
こういう理解していますが、誤った理解をしている点がありましたらご指摘をお願いいたします。

17
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?