これはなに?
画面を下に引っ張って(pan down)してリロードする機能が欲しくて作りました。
このサンプルは、実際にはリロードせず、リロード処理が必要になった回数をカウントして表示しているだけです。
同じようなものはすでにいくつかあると思います。
もしかすると、すでにAngular Materialに入っているかもしれません。
薄目で見てやってください。
元ソース
こちら https://stackblitz.com/edit/angular-rxjs-pull-to-refresh を元にしています。
元ソースから以下の点を変更しています。
- アイコン(丸→矢印、ドロップシャドウ付加)
- リロード中にアイコン回転 → 引っ張っている最中にアイコン回転
- リロードが終わるとアイコンが消える → ただちにアイコンが消える
- RxJS 6対応
ソースコード
こちらに置きました。
主要ファイル
主なファイルです。
src
└ app
  ├ pull-to-refresh
  │ └ pull-to-refresh.component.ts  タッチイベントを受け取りアイコンを表示するコンポーネント
  └ services 
    └ load-notify.service.ts 他コンポーネント等へリロードすることを通知するためのサービス
ちょっと解説
自分でもよくわからなくなりそうなRxJS周りの処理を解説します。
捕まえるイベント
下記は、タッチイベントを拾うObservableです。
  private readonly touchstart$ = fromEvent<TouchEvent>(document, 'touchstart');
  private readonly touchend$ = fromEvent<TouchEvent>(document, 'touchend');
  private readonly touchmove$ = fromEvent<TouchEvent>(document, 'touchmove');
アイコンの場所と回転の制御
下記は、アイコンの移動と回転をさせるためのパラメータを流すObservableです。
touchstartイベントが流れたら、代わりにtouchmoveイベントを購読し、移動量を流すようにしています。
touchendイベントが流れるまで購読し終わったら、現在位置から画面トップまでマイナス方向の値を流します。
  private drag$ = this.touchstart$.pipe(
    switchMap(start => {
      let pos = TOP_POSITION;
      return concat(
        this.touchmove$.pipe(
          map(move => move.touches[0].pageY - start.touches[0].pageY),
          tap(p => (pos = p)),
          filter(p => p < this.pullDistance),
          takeUntil(this.touchend$)
        ),
        defer(() => this.tweenObservable(pos, TOP_POSITION, 200)) // 位置を戻す
      );
    }),
    repeat()
  );
リロードするように通知
下記は、コンポーネント初期化時のコールバックです。
サービスを介して画面のリロードが必要になったことを通知します。
touchstartイベントが流れたら、代わりにtouchendイベントを購読し、移動量を流しています。
移動量が規定値以上であれば画面リロードを通知します。
  ngOnInit(): void {
    // 指を離した時に、規定距離を移動していたらリフレッシュ
    this.touchstart$
      .pipe(
        switchMap(start => {
          return this.touchend$.pipe(
            map(x => x.changedTouches[0].pageY - start.touches[0].pageY)
          );
        }),
        filter(p => p >= this.pullDistance)
      )
      .subscribe(() => this.loadNotifyService.notify());
  }
感想
- HammerJSとAngular組み込みのアニメーション機能を使って実装していましたが、どうにも期待する動きになりませんでした。
- イベントを絡めたコンポーネントのユニットテストは難しいです(すみません。今回書いてません)。
- RxJSたのしぃ〜。
最後までお読みいただきありがとうございました。
