Angularでポプテピピックをしてみた

# 前提
元ネタに便乗(https://renidentia991.bitbucket.io/pptp.html)
して、同じものを勉強中のAngular,RxJSで作成してみた。



# 要件
 1. ボタンを押すと、ポ,プ,テ,ピピックのいずれかが一定時間ごとに選択される
 * ↑を末尾に追加した文字列を画面に表示
 * 表示文字列の末尾が'ポプテピピック'になるとAAを表示し、文字列の追加をやめる

# 作っていく

## 方針
イベントを契機に、一定時間ごとに処理が走るのでRxJSを使うとうまく書けそう
→ Angularで書く

## 準備
angular-cliで雛形を作成。

```zsh:angular-cli
cd work
ng new popTeamEpic
cd popTeamEpic
```

動作確認をする。

```zsh:angular-cli
ng serve
```
`localhost:4200`にアクセスし、デフォルトページが表示されることを確認する。

## htmlの準備
`app/src/app.component.html`に書いていく。

```html:app.component.html
<h1>
    「ポ」「プ」「テ」「ピピック」をランダムに出力して「ポプテピピック」が
    完成したら竹●房を破壊するAngular
</h1>
<button
    (click)="start()"
>クリックしてね
</button>

<p>{{displayString}}</p>

<div　*ngIf="isPopTeamEpic(this.displayString,this.targetString)">
<p
    style="font-family:'ＭＳ Ｐゴシック',sans-serif;"
>
　　　　　　　　　　　　　　　AAは省略
</p>
</div>

```

`クリックしてね`ボタンを押下後、`start()`によって文字の表示が始まる。


## コンポーネントに処理を書いていく

### 必要な定数と変数の準備
```typescript:app.component.ts
import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent{
    outputStrings:Array<string> = ['ポ','プ','テ','ピピック'];
    targetString:string = 'ポプテピピック';       
    displayString:string='';
    popTeamEpicObservable:Observable<string> = new Observable();
    
}
```

各変数の意味は省略。

### Observableの設定
`Observable`は簡単に表すと、何かしらを契機として値を`Observer`に流し始めるオブジェクト。
今回は`popTeamEpicObservable`にポ,プ,テ,ピピックを流す設定をする。


```typescript:app.component.ts
import { OnInit,Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/takeWhile';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
    outputStrings:Array<string> = ['ポ','プ','テ','ピピック'];
    targetString:string = 'ポプテピピック';       
    displayString:string='';
    popTeamEpicObservable:Observable<string> = new Observable();
    
    ngOnInit():void{
        // 一定期間ごとに文字列をstreamするObservableを設定
        this.popTeamEpicObservable = Observable
            .interval(100) // 一定時間(ms)ごとに流す
            .map(
                ()=>this.getPopTeamEpic(this.outputStrings)
            ) //outputStringのいずれか一つを流す
            .takeWhile(
                (str)=> !this.isPopTeamEpic(this.displayString
                                            ,this.targetString)
            ); //表示文字列の末尾が'ポプテピピック'になるまで
    }

    // strArray(ポ,プ,テ,ピピック)のいずれかの文字列を返す関数
    private getPopTeamEpic(strArray:Array<string>):string{
        return strArray[
            Math.floor(Math.random()*(strArray.length))
        ];
    }

    // 表示文字列の末尾がポ,プ,テ,ピピックかどうか判定する関数
    public isPopTeamEpic(str:string,targetString:string):boolean{
        return str.endsWith(targetString);
    }
```

`Observable`は`subscribe()`されると処理が実行されて値が下流に流されるが、初期化処理ではまだ`Observer`が`subscribe()`しないので値が流れない。
ここでは`Operators`によって流す値や周期、条件の設定をしている。
今回利用した`Operators`は下記の通り。

| Operator名 | 処理 | 
|:-----------|:------------|
| interval       | 一定時間ごとに値を流す。 今回は100msで設定。       | 
| map     | 流れてきた値を加工する。今回は値を加工せず, ポ,プ,テ,ピピックのいずれかを流すように設定。    |
| takeWhile       | 条件を満たす間、値を流す。今回は、表示文字列の末尾がポプテピピックでないことを条件に設定。        | 

まとめると、`popTeamEpicObservable`は`100msごとにポ,プ,テ,ピピックのいずれかを流し、表示文字列の末尾がポプテピピックになったら完了`という`Observable`になる。

この段階ではまだ`Observer`によって`subscribe()`されていないため、文字列は表示され始めない。
次に、流された後の処理、つまり`Observer`の処理を追加する。

### Observerの設定
前項で設定した`popTeamEpicObservable`の値を受け取るために、`start()`関数を作成して`Observer`を設定する。

```typescript:app.component.ts
start():void{
        // ポ,プ,テ,ピピックが流れ始める
        this.popTeamEpicObservable
            .subscribe(
                // onNext() 表示文字列にポ,プ,テ,ピピックを追加
                (str)=> this.displayString+= str, 
                // onError() 
                (err)=> console.log(err),
                // onComplete()
                ()=> console.log('complete')
            );
    }
```

`Observer`の持つ`subscribe()`メソッドは３つの関数を設定できる。

| 関数 | 処理 | 
|:-----------|:------------|
| onNext()       |  `Observable`から正常に値が流れてきた場合の処理 | 
| onError()     | `Observable` が途中でエラーとなった場合の処理|
| onComplete()      |`Observable`が完了した後の処理 | 

`Observable`が値を流す時点で既に文字を追加する周期や条件が設定されているので、ここでは`onNext()`に文字列の結合処理を書くだけで良い。
また、表示文字列の末尾が`ポプテピピック`となった場合は`popTeamEpicObservable`が完了するので,`onComplete()`が実行され文字列の結合が終わる。

# 出来上がったもの
ポ,プ,テ,ピピックが揃うととサムシングが爆発する。
![popteamepic.herokuapp.com_(iPad Pro).png](https://qiita-image-store.s3.amazonaws.com/0/246528/edf9b9ac-890e-eca5-4361-fd85a06b732b.png)

* github : https://github.com/lilik2229/popTeamEpic
* DEMO   : https://popteamepic.herokuapp.com/

# 感想
RxJSを使うとif文,for文も使わずに書くことが出来る。
条件分岐が増えると可読性、保守性が下がってつらいので、全く使わずに実装できるのはとても良い。
とっつきにくいように見えるが使いこなせると強力な武器になると思うので、積極的に勉強していきたい。


# 参考
* https://renidentia991.bitbucket.io/pptp.html
* http://reactivex.io/rxjs/
* https://app.codegrid.net/entry/2017-rxjs-1
* https://tech.recruit-mp.co.jp/front-end/rxjs-intro/