Angularチュートリアルをやってみて -3-

チュートリアルを一通りやってみたが、よく分からなかった。 理解を深めるために、やった内容を書き出してみる。
今回はサービスについて復習する。

サービス

そもそも「サービス」とはなんなのか。チュートリアルでは、下記のように説明されている。

サービスは、お互いを知らない クラスの間で情報を共有する最適な方法です。

Angularではコンポーネントでデータの取得や更新をすべきではないので、データの取得や更新を行う「サービス」を使うらしい。
サービスを作成してみる。

ng generate service message

作成された雛型はこうなった。

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

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

  constructor() { }
}

ここで、@Injectableが大事そう。チュートリアルでは、下記のように説明されている。

クラスを依存関係注入システムに参加するものとしてマークします。

意味が分からないので、調べてみると下記のように説明されていた。

サービスクラスであることの条件は、@Injectableデコレーターで修飾されていることです。@Injectableデコレーターを付与することで、クラスをサービスとしてコンポーネントに引き渡せるようになります。

つまり、@Injectableがあれば「サービス」になると解釈することができる。@InjectableprovidedInがあり、'root'の場合は、ルートでのインスタンスとなる。

ボタン「メッセージ」で「msg1」、ボタン「メッセージ2」で「msg2」をテキストボックスに入力した文字列に追加する機能を作成する。

//message.service.ts
import { Injectable} from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class MessageService {
  private message: string = "";

  public add(intext: string): void{
    this.message = intext + "_msg1";
  }

  public add2(intext: string): void{
    this.message = intext + "_msg2";
  }

  public get(): string{
    return this.message
  }
  constructor() { }
}

add()により「msg1」を追加し、add2()により「msg2」を追加する。get()は表示のためにmessageを返すメソッドとなっている。
呼び出すコンポーネントは次のように作成した。

//app.component.ts
import { Component, OnInit } from '@angular/core';
import { MessageService } from './message.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
  public text: string = "";
  public outtext: string = "";

  constructor(private mService: MessageService) { }

  ngOnInit(): void{
  }

  public show(){
    this.mService.add(this.text);
    this.outtext = this.mService.get();
  }

  public show2(){
    this.mService.add2(this.text);
    this.outtext = this.mService.get();
  }
}
<!--app.component.html-->
<input type="text" [(ngModel)]="text" />
<br>
<label>{{text}}</label>
<br>
<input type="button" (click)="show()" value="メッセージ" /><br>
<input type="button" (click)="show2()" value="メッセージ2" /><br>
<label>{{outtext}}</label>

HTMLでは2つのボタンを設定し、それぞれshow()show2()をクリック時に呼び出す。これで、ボタンを押すとテキストボックスの入力値に対して、文字列を追加する。
f:id:kopai:20190218115956p:plain
これを変更して、ボタンを押すたびに増えていくように変更する。

//message.service.ts
import { Injectable} from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class MessageService {
  private message: string = "";

  public add(intext: string): void{
    this.message = this.message + intext + "_msg1";
  }

  public add2(intext: string): void{
    this.message = this.message + intext + "_msg2";
  }

  public get(): string{
    return this.message
  }
  constructor() { }
}

サービスの内容を変更した。サービスの変数に対して、テキストボックスの入力内容を追加していくようにした。
次に、このサービスを2つのコンポーネントから利用する。コードを以下のように変更した。

<!--app.component.html-->
<app-a-component></app-a-component>
<app-b-component></app-b-component>
//a-component.component.ts
import { Component, OnInit } from '@angular/core';
import { MessageService } from '../message.service';

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

  constructor(private mService : MessageService) { }
  public text: string= "";
  public outtext: string="";

  ngOnInit() {
  }

  public show(){
    this.mService.add(this.text);
    this.outtext = this.mService.get();
  }

  public show2(){
    this.mService.add2(this.text);
    this.outtext = this.mService.get();
  }
}
<p>
  a-component works!
</p>

<input type="text" [(ngModel)]="text" />
<br>
<label>{{text}}</label>
<br>
<input type="button" (click)="show()" value="メッセージ" /><br>
<input type="button" (click)="show2()" value="メッセージ2" /><br>
<label>{{outtext}}</label>
//b-component.component.ts
import { Component, OnInit } from '@angular/core';
import { MessageService } from '../message.service';

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

  constructor(private mService : MessageService) { }
  public text: string= "";
  public outtext: string="";

  ngOnInit() {
  }

  public show(){
    this.mService.add(this.text);
    this.outtext = this.mService.get();
  }

  public show2(){
    this.mService.add2(this.text);
    this.outtext = this.mService.get();
  }
}
<p>
  b-component works!
</p>

<input type="text" [(ngModel)]="text" />
<br>
<label>{{text}}</label>
<br>
<input type="button" (click)="show()" value="メッセージ" /><br>
<input type="button" (click)="show2()" value="メッセージ2" /><br>
<label>{{outtext}}</label>

aとbの2つのコンポーネントを用意し、それぞれにボタンを配置した。実行すると、出力される文字列はaとbで共有されていることが分かる。これは、サービスの宣言部分で、

@Injectable({
  providedIn: 'root'
})

上記のように宣言しているためである。この宣言では、'root'つまり、最上位での宣言を行っているため、aとbのそれぞれでサービスを作成していることにはなっていない。
これをaとbで独立したサービスとするためには、次のように宣言する。

//a-component.component.ts
@Component({
  selector: 'app-a-component',
  templateUrl: './a-component.component.html',
  styleUrls: ['./a-component.component.css'],
  providers: [MessageService]
})
//b-component.component.ts
@Component({
  selector: 'app-b-component',
  templateUrl: './b-component.component.html',
  styleUrls: ['./b-component.component.css'],
  providers: [MessageService]
})

これにより、別々の独立したサービスとすることができる。