Angularでのデータ共有方法

現在業務で使用するためAngularの学習を行っております。Angularではいくつかのデータの共有方法があったので理解の整理のために投稿します。間違い等あればご指摘ください。

# Angularにおけるデータ共有方法
Angularでは主に以下のようなデータを共有する方法があります。
- @Input,@Outputを使用した、親コンポーネントと子コンポーネント間でのデータ共有
- URLパラメータ化
- サービスの使用

## 親コンポーネントと子コンポーネント間でのデータ共有
Angularでは親コンポーネントと子コンポーネントでデータを共有する際には@Input,@Outputを使用します。
以下に具体的な使用例を示します。

### 親コンポーネントのデータを子コンポーネントに共有する
ここでは親コンポーネントがtop.component.ts,子コンポーネントがheader.component.tsとします。

```typescript:top.component.ts（親）
import { Component } from '@angular/core';

@Component({
  selector: 'app-top',
  templateUrl: './top.component.html',
  styleUrls: ['./top.component.css']
})
export class TopComponent {
  //子コンポーネントと共有したい変数
  sharedItem:string=""; 
}
```
```html:top.component.html（親）
<app-header [item]="sharedItem"></app-header>
<input type="text" [(ngModel)]="this.sharedItem">
```



```typescript:header.component.ts（子）
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
export class HeaderComponent {
  //親コンポーネントから共有されるデータを受け取る
  @Input() item = '';
}
```


```html:header.component.html（子）
<p>
  Shared item: {{item}}
</p>
```

親コンポーネントで子コンポーネントと共有したい変数を定義します。そして、親HTMLで以下のように定義します。
```text:親コンポーネントHTML
<selector名 [子コンポーネントで受け取る名]="親コンポーネントで定義した変数名"><</selector名>
```
と定義すれば親子間でデータを共有することができます。今回はユーザの入力内容がsharedItemに表示されるようにしました。

![スクリーンショット 2023-11-03 16.40.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458671/aa768502-3c76-ebc9-531c-54fea16a8e2a.png)

### 子コンポーネントのデータを親コンポーネントに共有する
次は先ほどの逆で子コンポーネントのデータを親コンポーネントに共有する方法についてまとめます。



```html:header.component.html（子）
<label for="item-input">Add an item:</label>
<input type="text" id="item-input" #newItem>
<button type="button" (click)="addNewItem(newItem.value)">Add to parent's list</button>
```

```typescript:header.component.ts（子）
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
export class HeaderComponent {
  @Output() newItemEvent = new EventEmitter<string>();

  addNewItem(value: string) {
    this.newItemEvent.emit(value);
  }
}
```
子コンポーネントでは@Outputを使用して、親コンポーネントにイベントを通知します。この際、独自のイベントを定義できる、EventEmitterを使用します。EventEmitterについては以下の記事がわかりやすくまとめられています。

https://weseek.co.jp/tech/1359/

ボタンクリックでユーザの入力値を親コンポーネントに通知します。
次に親コンポーネントについてまとめます。




```typescript:top.component.ts（親）
import { Component } from '@angular/core';

@Component({
  selector: 'app-top',
  templateUrl: './top.component.html',
  styleUrls: ['./top.component.css']
})
export class TopComponent {
  items = ['item1', 'item2', 'item3', 'item4'];

  addItem(newItem: string) {
    this.items.push(newItem);
  }
}
```
親コンポーネントでは受け取った入力値を受け取ります。

```html:top.component.html（親）
<app-header (newItemEvent)="addItem($event)"></app-header>
<ul>
  <li *ngFor="let item of items">{{item}}</li>
</ul>
```

```text:親HTML
<selector名 (子コンポーネントで定義したイベント名)="親コンポーネントで定義したメソッド"></selector名>
```
このようにして子コンポーネントのデータを親コンポーネントに渡します。






## URLのパラメータで共有する
例えばある画面Aから次の画面Bに遷移するときに画面Aでの入力内容などを画面Bに持っていきたい場合などでは、URLのパラメータに共有したい情報を持たせることができます。
以下の記事でわかりやすくまとまっています。

https://www.digitalocean.com/community/tutorials/angular-query-parameters-ja


## サービスを利用する
親子間でデータ共有を行う場合は、@Input,@OUtputを使用すればいいのですが、これらはコンポーネントが初期化されたタイミングで共有していたデータも初期化されてしまいます。サービスはシングルトンなので、アプリケーションを再起動、停止などしない限りはデータが初期化されません。またデータを一元管理することもできます。私は、画面遷移を伴う場合など（コンポーネントが初期化されてしまい、@Input,@Outputが使用できない場合）にサービスを利用してデータを共有しています。



ここでは、share.service.tsを利用してservice1.component.tsとservice2.component.tsでデータを共有します。


```typescript:share.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ShareService {
  message:string;

  constructor() { }

  getMessage(){
    return this.message;

  }

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

}
```
次にservice1.component.ts,service2.component.tsを示します。


```typescript:service1.component.ts
import { ShareService } from './../../../service/share.service';
import { Component } from '@angular/core';

@Component({
  selector: 'app-service1',
  templateUrl: './service1.component.html',
  styleUrls: ['./service1.component.css']
})
export class Service1Component {
  message:string;
  constructor(private shareService:ShareService){}

  setMessage(){
    this.shareService.setMessage(this.message);
  }

}
```

```html:service1.component.html
<p>共有したい言葉を入れてください</p>
  <input type="text" name="message" [(ngModel)]="message">
  <button (click)="setMessage()">共有</button>
  <div *ngIf="message">{{message}}がサービスにセットされました</div>
<app-service2></app-service2>

```
![スクリーンショット 2023-11-03 18.23.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458671/09ff2d1f-af17-ef23-0f5e-096306558cdb.png)

service1コンポーネントでは、「共有」ボタンをクリックすると、ユーザが入力した文字をsetMessageメソッドでshare.service.tsのmessageにセットしています。


次にservice2コンポーネントを示します。
```typescript:service2.component.ts
import { Component } from '@angular/core';
import { ShareService } from 'src/service/share.service';

@Component({
  selector: 'app-service2',
  templateUrl: './service2.component.html',
  styleUrls: ['./service2.component.css']
})
export class Service2Component {
  message:string;
  constructor(private shareService:ShareService){}
  
  getMessage(){
    this.message=this.shareService.getMessage();
  }
}
```
```html:service2.component.html
<p>共有した言葉を表示する</p>
<button (click)="getMessage()">表示</button>
<div *ngIf="message">{{message}}がサービスにセットされています</div>
````
![スクリーンショット 2023-11-03 18.27.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458671/31115445-58fc-2b1f-7914-719cd49a6fc5.png)

「表示」ボタンをクリックすると、getMessageメソッドでユーザ入力値をshare.service.tsのmessageを表示しています。

このようにサービスを使用して、異なるコンポーネント間でデータを共有することができます。サービスではアプリケーションが再起動、停止しない限りデータは保持されるため、意図しないデータがセットされている場合があるので注意が必要です。(別の処理でサービスで共有した値があることに気づかず、時間を無駄にしました。。。)



## 参考資料
https://angular.jp/guide/inputs-outputs

https://qox.jp/blog/angular-course03/

https://qiita.com/Yamamoto0525/items/efc0e5617c7427fea86a