循環参照 ~Service同士のDIで気をつけるべきこと~~

# Serviceとは
AngularのServiceは、Componentでデータの受け渡しに集中するために、委譲されたロジックの部分の責務をもたせるというデザインパターンの一つです。
これによって、Componentのなかをシンプルにすることができます。

```typescript
import { Injectable } from '@angular/core';
import { Hero } from './hero';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { HttpClient, HttpHeaders } from '@angular/common/http';

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

  private heroesUrl = 'api/heroes';

  constructor(
    private http: HttpClient
  ) { }

  public getHero(id: number): Observable<Hero> {
    const url = `${this.heroesUrl}/${id}`;
    return this.http.get<Hero>(url)
      .pipe(
        tap(_ => this.log(`fetched hero id=${id}`)),
        catchError(this.handleError<Hero>(`getHero id=${id}`))
      );
  }

}

```
* Componentの記述がシンプルになり、保守性が高まります
* Componentで、Serviceを使うときはDI(Dependency Injection)をすることになります。(参考記事は[こちら](https://qiita.com/lacolaco/items/61eed550d1f6070b36ab))

```typescript
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.scss']
})
export class HeroesComponent implements OnInit {

  public heroes: Hero[];

  constructor(
    private heroService: HeroService
  ) { }

  ngOnInit() {
    this.getHeroes();
  }

  private getHeroes(): void {
    this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes);
  }

}

```
https://angular.jp/tutorial/toh-pt4
# Service同士でDependencyInjectionしてみる
Service同士でDependency Injectionすることも可能です。


##LoggerService
log関連のメソッドをもつService

```typescript
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { HttpClient, HttpHeaders } from '@angular/common/http';

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

  constructor(
  ) { }

  private add(message: string) {
    console.log(` ${message}`);
  }
}

```

## LoggerServiceをHeroServiceにDependencyInjectionする
errorが出たとき、logを出力します。
logを出力するというメソッドは、LoggerServiceに委譲するようにします。
これは,あるServiceからあるServiceへの一方向のDIとなります。

```typescript
import { Injectable } from '@angular/core';
import { Hero } from './hero';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { loggerService } from './logger.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';

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

  private heroesUrl = 'api/heroes';

  constructor(
    private http: HttpClient,
    private loggerService: LoggerService,
    private messageService: Message
  ) { }

  public getHero(id: number): Observable<Hero> {
    const url = `${this.heroesUrl}/${id}`;
　　private messageService: MessageService,
    return this.http.get<Hero>(url)
      .pipe(
        tap(_ => this.logggerService.add(`fetched hero id=${id}`)),
        catchError(this.handleError<Hero>(`getHero id=${id}`))
      );
  }

}

```


# 循環参照を起こすDIの仕方
では、相互間のDIの場合、どうなるのでしょうか。


## MessageService
heroServiceのgetHeroメソッドをaddUrlメソッドで使用しています。

```typescript
import { Injectable } from '@angular/core';
import { Message } from './message';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { heroService } from './hero.service';
import { loggerService } from './logger.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';

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

  constructor(
    private http: HttpClient,
    private heroService: HeroService,
    private loggerService: LoggerService
  ) { }

  public addUrl(url: string): Observable<Message> {
    this.heroService.getHero.subscribe(hero => {
     const message = `この${url}では、${hero}が取得できます`;
     this.loggerService.add(message);
     )
  }

}

```

## HeroService
messageServiceのaddUrlメソッドをgetHeroメソッドで使用しています。

```typescript
import { Injectable } from '@angular/core';
import { Hero } from './hero';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { messageService } from './message.service';
import { loggerService } from './logger.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';

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

  private heroesUrl = 'api/heroes';

  constructor(
    private http: HttpClient,
    private messageService: MessageService,
    private loggerService: LoggerService
  ) { }

  public getHero(id: number): Observable<Hero> {
    const url = `${this.heroesUrl}/${id}`;
    this.messageService.addUrl(url);
    return this.http.get<Hero>(url)
      .pipe(
        tap(_ => this.loggerService.add(`fetched hero id=${id}`)),
        catchError(this.handleError<Hero>(`getHero id=${id}`))
      );
  }

}

```

上記では、MessageServiceでHeroServiceの返り値を取得してるが、その取得するためにはMessageServiceの返り値を取得しなければいけないという**循環参照**に陥っています。
当然ながらerrorが吐き出されます。

```typescript
Uncaught Error: Can't resolve all parameters for HeroService: (?, [object Object], [object Object]).
    at syntaxError (webpack-internal:///306:684)
    at CompileMetadataResolver._getDependenciesMetadata (webpack-internal:///306:15764)
    at CompileMetadataResolver._getTypeMetadata (webpack-internal:///306:15599)
    at CompileMetadataResolver._getInjectableMetadata (webpack-internal:///306:15579)
    at CompileMetadataResolver.getProviderMetadata (webpack-internal:///306:15939)
    at eval (webpack-internal:///306:15850)
    at Array.forEach (<anonymous>)
    at CompileMetadataResolver._getProvidersMetadata (webpack-internal:///306:15810)
    at CompileMetadataResolver.getNgModuleMetadata (webpack-internal:///306:15378)
    at CompileMetadataResolver.getNgModuleSummary (webpack-internal:///306:15216)
```

# 注意するべきこと
このように相互間でのServiceは循環参照のerrorを起こす可能性があるので、注意してServiceをDependencyInjectionするべきです。

# 解決策
これへの解決策として中間Serviceを挟むなどがあります。
HogeServiceとFooServiceで循環参照を起こさないようにするために、MetaServiceを経由して、HogeServiceとFooServiceをやり取りするなどがあります。





# 参考記事
https://www.l08084.com/entry/2018/06/09/170727