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


前提

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

要件

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

作っていく

方針

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

準備

angular-cliで雛形を作成。

angular-cli
cd work
ng new popTeamEpic
cd popTeamEpic

動作確認をする。

angular-cli
ng serve

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

htmlの準備

app/src/app.component.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:'MS Pゴシック',sans-serif;"
>
               AAは省略
</p>
</div>

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

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

必要な定数と変数の準備

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にポ,プ,テ,ピピックを流す設定をする。

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);
    }

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

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

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

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

Observerの設定

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

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

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

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

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

出来上がったもの

ポ,プ,テ,ピピックが揃うととサムシングが爆発する。

感想

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

参考