使用Angular服务在组件之间共享数据
首先
我们将探讨在没有亲子关系的多个组件之间使用服务来共享数据的方法。
提供最新消息
2021年1月4日
- 記事内で扱ったコードを Angular v11.0.5 で確認しました
2018年5月26日 yī bā wǔ yuè èr shí liù rì)
-
- Angular のバージョンが 6 に上がったことで、 RxJS のバージョンも更新されました
- 上記に伴い、 RxJS の import パスも変更されました。つきましては、記事中のコードと github のコード をあわせて修正しました
工作环境
$ ng --version
Angular$ node --version
npm$ npm --version
$ ng version_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | ‘_ \ / _` | | | | |/ _` | ‘__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/
Angular CLI: 11.0.5
Node: 12.18.3
OS: darwin x64
Angular: 11.0.5
… animations, cli, common, compiler, compiler-cli, core, forms
… platform-browser, platform-browser-dynamic, router
Ivy Workspace: Yes
Package Version
———————————————————
@angular-devkit/architect 0.1100.5
@angular-devkit/build-angular 0.1100.5
@angular-devkit/core 11.0.5
@angular-devkit/schematics 11.0.5
@schematics/angular 11.0.5
@schematics/update 0.1100.5
rxjs 6.6.0
typescript 4.0.2
相关文章
-
- [Angular] Angular CLI によるサービスの生成
-
- [Angular] 親子関係にあるコンポーネント間でデータの受け渡しを行う
- [Angular] 子コンポーネントや外部コンテンツの参照を取得する
构成
本篇文章所涉及的结构如下所示。
src/
`-app/
`-sample1/
| `- sample1.component.css
| `- sample1.component.html
| `- sample1.component.ts
`-sample2/
| `- sample2.component.css
| `- sample2.component.html
| `- sample2.component.ts
`-service/
| `- common.service.ts
`- app.component.css
`- app.component.html
`- app.component.ts
`- app.module.ts
本次构成的重点在以下2点。
-
- サービスによるデータの共有を確認するため次の2コンポーネントを配置
-
- sample1.component
-
- sample2.component
-
- コンポーネント間でデータ共有を実現するため次のサービスを配置
- common.service
在这些组件和服务中,需要确认组件之间的数据共享。
服务的实施
通用服务
import { Injectable } from '@angular/core';
// イベント発火のための Subject を import
// Angular Ver.6.x.x では rxjs から直接importするように変更された
import { Subject } from 'rxjs';
// こちらは Angular 6.x.x だとビルドエラーとなる
//import { Subject } from 'rxjs/Subject';
@Injectable()
export class CommonService {
/**
* データの変更を通知するためのオブジェクト
*
* @private
* @memberof CommonService
*/
private sharedDataSource = new Subject<string>();
/**
* Subscribe するためのプロパティ
* `- コンポーネント間で共有するためのプロパティ
*
* @memberof CommonService
*/
public sharedDataSource$ = this.sharedDataSource.asObservable();
/**
* コンストラクタ. CommonService のインスタンスを生成する
*
* @memberof CommonService
*/
constructor() {}
/**
* データの更新イベント
*
* @param {string} updateed 更新データ
* @memberof CommonService
*/
public onNotifySharedDataChanged(updateed: string) {
console.log('[CommonService] onNotifySharedDataChanged fired.');
this.sharedDataSource.next(updateed);
}
}
我们将按顺序逐一查看以下要点。
-
- Subject 的导入
从rxjs中导入Subject。
在下面的“声明Subject类型的属性”中需要导入Subject。
在本文中,我们将使用Subject来实现数据共享,以及在后续的组件中处理订阅。
声明Subject类型的属性
声明Subject类型的属性
private sharedDataSource = new Subject();
在下面的“声明订阅属性”和“数据更新事件”中,声明并创建一个Subject实例。
使用该属性来通知共享数据的变化。
声明订阅属性
声明订阅属性
public sharedDataSource$ = this.sharedDataSource.asObservable();
从先前创建的sharedDataSource对象中,使用asObservable()方法创建一个新的对象。
通过在处理数据共享的组件中进行订阅(如下述),使用asObservable()生成的对象来实现数据共享的机制。
数据更新事件
数据更新事件
public onNotifySharedDataChanged(updated: string) {
this.sharedDataSource.next(updated);
}
我们从先前创建的sharedDataSource对象中调用next()方法。
这个方法是用于从处理数据共享的组件中调用的,它将参数updated直接设置为next()的参数。
通过调用this.sharedDataSource.next(updated)来执行该方法,参数的数据将传递给订阅(如下述)等待接收数据的组件。
组件的实现
样本1的组件
import { Component, OnInit, OnDestroy } from '@angular/core';
// subscribe を保持するための Subscription を import
// Angular Ver.6.x.x では rxjs から直接importするように変更された
import { Subscription } from 'rxjs';
// こちらは Angular 6.x.x だとビルドエラーとなる
//import { Subscription } from 'rxjs/Subscription';
// サービスを登録するための import
// アプリ全体でのサービスの共有、コンポーネント単位でのサービスの共有に関わらず、ここの import は必要
import { CommonService } from '../service/common.service';
@Component({
selector: 'app-sample1',
templateUrl: './sample1.component.html',
styleUrls: ['./sample1.component.css'],
})
export class Sample1Component implements OnInit, OnDestroy {
/**
* CommonService の変数の参照を取得するプロパティ
*
* @type {String}
* @memberof Sample1Component
*/
public serviceProp: String = 'Initialized by Sample1Component';
/**
* subscribe を保持するための Subscription
*
* @private
* @type {Subscription}
* @memberof Sample1Component
*/
private subscription!: Subscription;
/**
* コンストラクタ. ServiceSample1Component のインスタンスを生成する
*
* @param {CommonService} commonService 共通サービス
* @memberof Sample1Component
*/
constructor(private commonService: CommonService) { }
/**
* ライフサイクルメソッド。コンポーネントの初期化で使用する
*
* @memberof Sample1Component
*/
ngOnInit() {
// イベント登録
// サービスで共有しているデータが更新されたら発火されるイベントをキャッチする
this.subscription = this.commonService.sharedDataSource$.subscribe(
msg => {
console.log('[Sample1Component] shared data updated.');
this.serviceProp = msg;
}
);
}
/**
* コンポーネント終了時の処理
*
* @memberof Sample1Component
*/
ngOnDestroy() {
// リソースリーク防止のため CommonService から subcribe したオブジェクトを破棄する
this.subscription.unsubscribe();
}
/**
* ボタンクリック時のイベントハンドラ
*
* @memberof Sample1Component
*/
onClicSendMessage() {
// CommonService のデータ更新を行う
console.log('[Sample1Component] onClicSendMessage fired.');
this.commonService.onNotifySharedDataChanged('Updated by Sample1Component.');
}
}
<p>
CommonService で設定されている値は「 {{this.serviceProp}} 」です。
</p>
<input type="button" value="send message" (click)="onClicSendMessage()" />
请按顺序查看以下要点。
关于 CommonService 的 DI,请参考相关文章,点击 这里 ,本文不做详述。
-
- 订阅的导入
订阅的导入
// 省略
import { Subscription } from ‘rxjs’;
// 省略
private subscription!: Subscription
为了保持订阅,导入了Subscription,并声明了该属性。
注册用于订阅数据更新事件的事件处理程序
注册用于订阅数据更新事件的事件处理程序
this.subscription = this.commonService.sharedDataSource$.subscribe(
msg => {
this.serviceProp = msg;
}
);
在CommonService中,注册了一个事件处理程序来订阅在数据更新事件中触发的next()。在这里,msg是设置为next()参数的对象,并通过将其设置为自己的属性serviceProp,实现了通过CommonService共享和更新数据。将订阅对象设置为subscription是为了在下面的ngOnDestroy中销毁订阅内容。
通知CommonService进行数据更新
通知CommonService进行数据更新
onClicSendMessage() {
this.commonService.onNotifySharedDataChanged(‘Updated by Sample1Component.’);
}
用于接收界面上输入数据的事件处理程序。在这里,调用了CommonService中提供的方法onNotifySharedDataChanged()来将输入的数据发送到服务中。这里设置的参数将被视为要在组件之间共享的数据。
组件销毁时的后处理
组件销毁时的后处理
ngOnDestroy() {
this.subscription.unsubscribe();
}
在组件销毁时通过unsubscribe来取消订阅的内容以进行销毁。如果不这样做,注册的事件处理程序将会一直存在。
关于模板
关于模板,只有单向数据绑定和输入表单,没有特别需要注意的事项。
样本2.组件
由于sample2.component的实现与sample1.component几乎相同,因此只提取不同之处,并省略解释。
如需了解sample2.component的代码细节,请参考此处的上传内容。
// 省略
@Component({
selector: 'app-sample2',
templateUrl: './sample2.component.html',
styleUrls: ['./sample2.component.css']
})
export class Sample2Component implements OnInit, OnDestroy {
/**
* CommonService の変数の参照を取得するプロパティ
*
* @type {String}
* @memberof Sample2Component
*/
public serviceProp: String = 'Initialized by Sample2Component';
// 省略
/**
* ボタンクリック時のイベントハンドラ
*
* @memberof Sample2Component
*/
onClicSendMessage() {
console.log('[Sample2Component] onClicSendMessage fired.');
this.commonService.onNotifySharedDataChanged('Updated by Sample2Component.');
}
}
执行结果
1. 开机后立即

被方框标记的文字在 sample1.component 中显示为「Initialized by Sample1Component」(橙框),在 sample2.component 中显示为「Initialized by Sample2Component」(蓝框)。
执行 sample1.component 的 send message 操作

点击发送消息按钮后,通过CommonService发送的消息已经更新。此时可以确定,在sample1.component和sample2.component中,用样例1.component设置的消息”Updated by Sample1Component.”正在显示。
执行sample2.component的发送消息函数

点击发送消息按钮后,通过CommonService更新了括号内的文字。同时可以确认,括号中的文字”Updated by Sample2Component.”在sample1.component和sample2.component中都显示为sample1.component设置的消息。
综上所述
总结本文实施的数据共享机制可分为以下步骤:
-
- 在组件中修改数据
-
- 组件通过方法调用将修改后的数据传递给服务
-
- 服务接收修改后的数据并通过触发事件来展示数据
- 每个组件订阅触发的事件并更新自己的属性
换句话说,服务和组件之间通过方法调用和事件来实现数据共享。
尽管在本文中出现了”数据共享”一词,但我感觉到它与本文所述的实施方案之间存在一些不符之处,就此为止。
在相关文章中,通过引用组件和使用@Input / @Output进行数据传递时,需要有父子关系。但是,本文介绍的方法可以在没有父子关系的情况下实现数据共享。我认为,这在组件设计时无需担心父子关系的限制,是一个优点。
源代码
此次文章提及的用于验证操作的代码已经上传到这里,供参考。