Blog

ブログ

Angular講座:コンポーネント間の値受け渡し③

2022.07.01 公開
Momoka_Ide
Momoka_Ide プログラマ

おさらい

今回はサービスでのコンポーネント間の値の受け渡しについてサンプルを紹介していきます。

サービスの役割

Angularの公式チュートリアルに、コンポーネントはビューへの値受け渡しに集中し、データの取得や保存はサービスへ移譲すべきであると書かれています。

httpで通信する部分や、取得したデータを成形したりなど、難しいことはサービスに移して、コンポーネントのコードはスッキリさせよう!…というのがAngularが推奨しているアーキテクチャのようです。

このサービスを用いて、値を保持し、コンポーネント同士で値を共有することができます。

サービスを作る

普段サービスを作るときは 以下のコマンドで作ることができます

$ ng g service (サービス名)
@Injectable({
  providedIn: 'root'
})

このコマンドで作成されたサービスには上記のコードが含まれており、これはアプリ全体で使えるサービスですよ、ということを宣言しています。

アプリ全体で使えるサービスなので、どこのコンポーネントで呼び出しても、同じ値を共有することができます。

コンポーネント間で値を受け渡してみる

簡単なサンプルで値が渡せることを確認してみます。

緑背景部分と黒背景の部分がそれぞれ別コンポーネントになっていて、
フォームに入力した内容を、取得ボタンを押したときにサービスを通じて値を共有できることを確認するサンプルです。

cssについてはbootstrapを使用しているため、割愛します。

コンポーネント、サービスの作成

$ ng g c service-child
$ ng g c service-child2
$ ng g s service post

コンポーネント名などは好きにつけてくださって大丈夫です

作成したコンポーネントを、app-componentに配置します。

<app-service-child></app-service-child>
<app-service-child2></app-service-child2>

service-child

service-childに、入力フォームを配置。

// service-child.component.html
<form (submit)="submit()" class="m-5 bg-light" class="m-5 p-3 bg-info">
    <input name="message" class="mr-3" [(ngModel)]="message">
    <button type="submit" class="btn btn-primary">保存</button>
</form>
// service-child.component.ts
export class ServiceChildComponent implements OnInit {

  message:string;
  constructor(private postService:PostService) { }

  ngOnInit(): void {}

  submit(){
    this.postService.setMessage(this.message);
  }

}

service-child2

service-child2に、service-childで入力した文字列を表示します

// service-child2.component.html
<div class="m-5 p-3 bg-secondary">
  <p class="text-white">{{ message }}</p>
  <button type="button" (click)="getMessage()" class="btn btn-primary">
    取得
  </button>
</div>
// service-child2.component.ts
export class ServiceChild2Component implements OnInit {

  message:string;
  constructor(private postService:PostService) { }

  ngOnInit(): void {
  }

  getMessage(){
    this.message = this.postService.getMessage();
  }

}

service

サービスには値を共有するためのフィールドを用意。

export class PostService {
  private message:string;
  constructor() { }

  setMessage(message:string){
    this.message = message;
  }

  getMessage(){
    return this.message;
  }
}

コードは以上です。ng serve を行って確認してみてください。

サービスを使える範囲を絞る

最初の説明に、コマンドで作ったサービスはアプリ全体で使うことができるサービスであると説明しました。
使用範囲を絞ることもできます。

特定のモジュールでのみ使えるサービスにする

$ ng g s hoge
// @Injectable({
//   providedIn: 'root'   // <- コメントアウト
// })
export class HogeService {
  constructor() {}
}
@NgModule({
  declarations: [・・・],
  imports: [・・・],
  providers: [HogeService], // HogeModuleにあるコンポーネントでしか使えない
})
export class HogeModule {}

使いたいモジュールのprovidersにセット。

特定のコンポーネントのみ使用できるサービスにする

サービスのコメントアウトまではモジュールと同じです。

@Component({
  selector: 'app-hoge',
  templateUrl: './hoge.component.html',
  styleUrls: ['./hoge.component.scss'],
  providers: [HogeService],  // <- 追加
})
export class HogeComponent implements OnInit {
  constructor() {}

  ngOnInit(): void {}
}

注意すること

特定のモジュールでのみ使用できるように設定したサービスを、他のモジュールに設定しようとするとどうなるでしょうか。特にエラーにはならないので使うことはできるみたいです。

ですが、異なるコンポーネントやモジュールで同じサービスをproviderに指定していても、
同じサービスとして値は共有されません。コンポーネントではデストロイされるたびに、
サービスも破棄されているのか、そのコンポーネントが表示されるたびに値は初期化されていました。

モジュールはそのモジュールに所属しているコンポーネント間のみ、値が保持されているようです。

値が共有されないのに、同じサービス名で異なるコンポーネントやモジュールのproviderに使用するのは、バグの元になりそうでとても不親切だなと思いますので、1つのサービスがproviderとして所属する先は必ず1対1にする方が良さそうですね。

だからコマンドで作るサービスはデフォルトがrootになっているのでしょう……🤔