RxJSのfilterやmergeMapを活用してNEMのトランザクションデータをブラウザ上に表示する

前回はnem2-sdkをブラウザに使用できるようにして、簡単な情報を取得してみました。
今回は特定のアカウントからトランザクションデータを取得してみます。

その時の肝となる技術がRxJSです。

RxJS を学ぼう #1 - これからはじめる人のための導入編
https://tech.recruit-mp.co.jp/front-end/rxjs-intro/

Catapultからデータを取ってきた場合、基本的には以下のような処理を経て画面上に出力します。

```js
accountHttp
	.getAccountInfo(address)
	.subscribe(
		accountInfo => {
			$("#account_pubkey").text(accountInfo.publicKey);

		},
		err => console.error(err)
	);

```

accountHttpでノードに接続。getAccountInfoでアカウント情報を取りに行き、取れたらsubscribe内が処理されます。accountInfoに情報が入るので、適切な形に加工して画面に表示します。

では、取得したデータがリスト形式の場合はどんな方法で処理していくのでしょうか？
さきほどのソースコードは以下のような書式に書き換えることができます。

```js
accountHttp
	.outgoingTransactions(originAccount)
	.subscribe(
		function(transactions){
			console.log(transactions);	
		},
		err => console.error(err)
	);

```

少し見慣れた形になりましたね。今回はトランザクションデータを取得したので出力されたデータが配列で入ってきますので、同一項目のデータにコンバートをかけていくには、中でさらにループ処理を施す必要があります。

```js
accountHttp
	.outgoingTransactions(originAccount)
	.subscribe(
		function(transactions){
			transactions.some(function(tx){
				console.log(tx);	
			});
		},
		err => console.error(err)
	);
```

someの部分がループ処理になります。この内部にロジックを記述していくことで、どんな出力形式にでも対応していくことが可能です。
ただ、一括で変換をかけたいとき、あるいは処理対象でないレコードを除外したい場合は、RxJSのオペレータコマンドを利用して操作することが可能です。
以下のようにsubscribeの前にpipe()というセクションを作るとその中でmap、filter、などで一括操作が行えます。

```js
accountHttp
	.outgoingTransactions(originAccount)
	.pipe(
		rxjs.operators.mergeMap((_) => _),
		rxjs.operators.filter((_) => _.type === nem.TransactionType.TRANSFER), 
		rxjs.operators.toArray(),
	)
	.subscribe(
		transactions => {
			$('#items2-ctrl ul').append('<li>' + transactions.recipient.address +  '</li>');
		},
		err => console.error(err)
	);
```

順に説明していきます。

```js
rxjs.operators.mergeMap((_) => _),
```

mergeMapは配列[a,b,c] と格納されていたデータを[a],[b],[c]と置き換え、後続のオペレーションを適用しやすく変換します。

```js
rxjs.operators.filter((_) => _.type === nem.TransactionType.TRANSFER),
```

filter により、 transaction内部のtypeがTRANSFERのものに絞り込み、次の処理に通します。

```js
rxjs.operators.toArray(),
```

toArrayをかけると[a],[b],[c]を再び[a,b,c]という配列に戻します。
これらの操作により、subscribeへはTRANSFER以外のデータが除去されて届きます。

最後に指定namespaceIdを持つ送金の合計値を計算するサンプルを2例示しておきます。
最初はfilter内でやってしまう方法、もう一つはsubscribe内でやってしまう方法です。

```js

//一つ目
const namespaceId = new nem.NamespaceId([3294802500,2243684972]); 
accountHttp
	.outgoingTransactions(originAccount)
	.pipe(
		rxjs.operators.mergeMap((_) => _),
		rxjs.operators.filter((_) => _.type === nem.TransactionType.TRANSFER),
		rxjs.operators.map(function(tx){

			var amount = 0;
			tx.mosaics.some(function(mosaic) {
				if (mosaic.id.toString() == namespaceId.toString()) {
					amount = mosaic.amount.compact() / Math.pow(10, divisibility);
					return true;
				}
			});
			return amount;
		}),
		rxjs.operators.toArray(),
		rxjs.operators.map((_) => _.reduce((a, b) => a + b, 0))
	)
	.subscribe(
		total => {
			$('#account_transfer').text(total);
		},
		err => console.error(err)
	);


//二つ目
accountHttp
	.outgoingTransactions(originAccount)
	.pipe(
		rxjs.operators.mergeMap((_) => _),
		rxjs.operators.filter((_) => _.type === nem.TransactionType.TRANSFER),
		rxjs.operators.toArray(), // Add all mosaics amounts into one array
	)
	.subscribe(

		_ => {

			var amount = 0;
			_.some(function(tx) {

				tx.mosaics.some(function(mosaic) {
					if (mosaic.id.toString() == namespaceId.toString()) {
						amount += mosaic.amount.compact() / Math.pow(10, divisibility);
						return true;
					}
				});

			});
		},
		err => console.error(err)
	);

```

次回はnem2-sdkを利用したアカウント作成について説明します。


```html:htmlサンプル
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0">
<title>011_account_sample</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://unpkg.com/rxjs/bundles/rxjs.umd.min.js"></script>
<script src="nem2-sdk-0.11.6.js"></script><!-- Catapult Cow -->
<script>
$(function() {
	const nem = require("/home/ec2-user/node_modules/nem2-sdk");
	const NODE_URL = "http://13.114.200.132:3000"; // nf-testnet
	const accountHttp = new nem.AccountHttp(NODE_URL);
	const originPublicKey = 'AC1A6E1D8DE5B17D2C6B1293F1CAD3829EEACF38D09311BB3C8E5A880092DE26';
	const originAccount = nem.PublicAccount.createFromPublicKey(originPublicKey, nem.NetworkType.MIJIN_TEST);

	const recipientAddress = 'SB2Y5ND4FDLBIO5KHXTKRWODDG2QHIN73DTYT2PC';
	const address = nem.Address.createFromRawAddress(recipientAddress);

	const divisibility = 6; // Replace with mosaic divisibility

	$('#account_address').text(recipientAddress);
	$('#account_pubkey').text(originPublicKey);

	accountHttp
		.outgoingTransactions(originAccount, new nem.QueryParams(100))
		.pipe(
			rxjs.operators.mergeMap((_) => _), // Transform transaction array to single transactions to process them
			rxjs.operators.filter((_) => _.type === nem.TransactionType.TRANSFER), // Filter transfer transactions
		)
		.subscribe(
			transactions => {
				console.log(transactions.message.payload);

				$('#items2-ctrl ul').append('<li>' + transactions.recipient.address +  '</li>');
			},
			err => console.error(err)
		);

	const namespaceId = new nem.NamespaceId([3294802500,2243684972]); 
	accountHttp
		.outgoingTransactions(originAccount, new nem.QueryParams(100))
		.pipe(
			rxjs.operators.mergeMap((_) => _),
			rxjs.operators.filter((_) => _.type === nem.TransactionType.TRANSFER),
			rxjs.operators.map(function(tx){

				var amount = 0;
				tx.mosaics.some(function(mosaic) {
					if (mosaic.id.toString() == namespaceId.toString()) {
						amount = mosaic.amount.compact() / Math.pow(10, divisibility);
						return true;
					}
				});
				return amount;
			}),
			rxjs.operators.toArray(),
			rxjs.operators.map((_) => _.reduce((a, b) => a + b, 0))
		)
		.subscribe(
			total => {
				$('#account_transfer').text(total);
			},
			err => console.error(err)
		);

});
</script>
</head>
<body>
	<div class="container">

		<h1>011_account_transfer</h1>
		<div id="items-ctrl">
			<ul>
				<li>[ADDRESS]：<span id="account_address"></span></li>
				<li>[PUBLIC_KEY]：<span id="account_pubkey"></span></li>
				<li>[OUTGOING_SUM]：<span id="account_transfer"></span></li>
			</ul>
		</div>

		<h1>transactions</h1>
		<div id="items2-ctrl">
			<ul>
			</ul>
		</div>

	</div>
</body>
</html>

```