【実践ガイド】Angular 2 コンポーネント間データ共有:サービス、共有ステート、ルーティングなどを活用
Angular 2 でコンポーネント間でデータを共有する方法
@Input と @Output
@Input は、親コンポーネントから子コンポーネントへデータを一方方向に送信するために使用されます。親コンポーネントで @Input()
デコレータ付きのプロパティを定義し、子コンポーネントのテンプレートでバインディングすることで、親コンポーネントのプロパティ値を子コンポーネントに渡すことができます。
// 親コンポーネント (parent.component.ts)
export class ParentComponent {
name = 'Angular 2';
}
// 子コンポーネント (child.component.ts)
export class ChildComponent {
@Input() name: string;
}
// 子コンポーネント (child.component.ts)
export class ChildComponent {
@Output() nameChanged = new EventEmitter<string>();
changeName() {
this.nameChanged.emit('New Name');
}
}
// 親コンポーネント (parent.component.ts)
export class ParentComponent {
name = 'Angular 2';
constructor(private child: ChildComponent) {
this.child.nameChanged.subscribe(newName => {
this.name = newName;
});
}
}
サービス
サービスは、コンポーネント間で共有するデータやロジックを格納するために使用されます。コンポーネントはサービスをインジェクションすることで、サービスのデータやメソッドにアクセスすることができます。
// サービス (data.service.ts)
export class DataService {
private data = 'Hello from Service';
getData() {
return this.data;
}
setData(newData: string) {
this.data = newData;
}
}
// コンポーネント (component.component.ts)
export class MyComponent {
constructor(private dataService: DataService) {}
ngOnInit() {
console.log(this.dataService.getData()); // 'Hello from Service' を出力
this.dataService.setData('New Data');
}
}
共有ステート
NgRx などのライブラリを使用して、コンポーネント間で共有ステートを管理することもできます。共有ステートは、アプリケーション全体で一貫したデータの状態を維持するのに役立ちます。
ルーティング
コンポーネント間でデータを共有するもう 1 つの方法は、ルーティングを使用することです。コンポーネントに渡すデータをルートパラメータまたはクエリオブジェクトとして格納できます。
// 親コンポーネント (parent.component.ts)
this.router.navigate(['child', { data: { message: 'Hello from Parent' } }]);
// 子コンポーネント (child.component.ts)
constructor(private route: ActivatedRoute) {
this.message = this.route.snapshot.data['message'];
}
それぞれの方法の使い分け
- ルーティング
コンポーネント間で少量のデータを共有する場合に適しています。 - 共有ステート
アプリケーション全体で一貫したデータ状態を維持する必要がある場合に適しています。 - サービス
共有するデータやロジックが複雑な場合に適しています。 - @Input と @Output
親から子へのデータ送信、子から親へのイベント発行に適しています。
親コンポーネント (parent.component.ts)
export class ParentComponent {
name = 'Angular 2';
}
子コンポーネント (child.component.html)
<p>名前: {{ name }}</p>
export class ChildComponent {
@Input() name: string;
}
<child [name]="name"></child>
2 子から親へのイベント発行
export class ChildComponent {
@Output() nameChanged = new EventEmitter<string>();
changeName() {
this.nameChanged.emit('New Name');
}
}
<child (nameChanged)="onNameChanged($event)"></child>
export class ParentComponent {
name = 'Angular 2';
onNameChanged(newName: string) {
this.name = newName;
}
}
サービス (data.service.ts)
export class DataService {
private data = 'Hello from Service';
getData() {
return this.data;
}
setData(newData: string) {
this.data = newData;
}
}
export class MyComponent {
constructor(private dataService: DataService) {}
ngOnInit() {
console.log(this.dataService.getData()); // 'Hello from Service' を出力
this.dataService.setData('New Data');
}
}
共有ステート (NgRx を使用する場合)
NgRx ストア定義 (app.store.ts)
import { Injectable } from '@angular/core';
import { StoreConfig } from '@ngrx/store';
import { counterReducer } from './counter.reducer';
export interface AppState {
count: number;
}
@Injectable({
providedIn: 'root'
})
export class AppStore {
constructor(
@StoreConfig({ stateName: 'app', reducer: counterReducer })
private store: Store<AppState>
) {}
getState() {
return this.store.select();
}
dispatch(action: any) {
this.store.dispatch(action);
}
}
カウンターコンポーネント (counter.component.ts)
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '../app.store';
import { increment, decrement } from '../counter.actions';
@Component({
selector: 'app-counter',
templateUrl: './counter.component.html',
styleUrls: ['./counter.component.css']
})
export class CounterComponent implements OnInit {
count: number;
constructor(private store: Store<AppState>) {}
ngOnInit() {
this.store.select('count').subscribe(count => (this.count = count));
}
onIncrement() {
this.store.dispatch(increment());
}
onDecrement() {
this.store.dispatch(decrement());
}
}
<p>カウント: {{ count }}</p>
<button (click)="onIncrement()">+</button>
<button (click)="onDecrement()">-</button>
export class ParentComponent {
constructor(private router: Router) {}
goToChild() {
this.router.navigate(['child', { data: { message: 'Hello
ViewChild ディレクティブを使用して、親コンポーネントから子コンポーネントのインスタンスに直接アクセスし、そのプロパティやメソッドを操作することができます。ただし、この方法はコンポーネント間の結合を強くするため、あまり推奨されていません。
イベントバブリング
イベントバブリングを使用して、子コンポーネントから発生したイベントを親コンポーネントで処理することができます。ただし、この方法はイベントの伝達経路が複雑になる場合があるため、注意が必要です。
グローバル変数
アプリケーション全体で共有する必要がある少量のデータの場合は、グローバル変数を使用することができます。ただし、この方法はコードの可読性と保守性を低下させるため、最後の手段として使用するようにしてください。
それぞれの方法の注意点
- グローバル変数
コードの可読性と保守性が低下する可能性があります。 - イベントバブリング
イベントの伝達経路が複雑になり、デバッグが困難になる可能性があります。 - ViewChild
コンポーネント間の結合が強くなり、テストが困難になる可能性があります。
これらの方法をいつ使用するべきか
- グローバル変数
アプリケーション全体で共有する必要がある少量のデータの場合にのみ使用してください。 - イベントバブリング
親コンポーネントで子コンポーネントの発生したイベントを処理する必要がある場合にのみ使用してください。 - ViewChild
子コンポーネントの DOM 要素に直接アクセスする必要がある場合にのみ使用してください。
angular typescript