Ionic4で画面遷移アニメーションの方向をIonic3のように使い分けたい

Ionic4では画面遷移の実装が変更された影響か、Ionic3のような画面遷移アニメーションの変更ができなくなってしまった。この問題に対する暫定的な対処方法を説明する。

なお、Ionic 4.1.2 + Angularの構成で、画面遷移に`<ion-router-outlet>`を主に利用している前提とする。

# Ionicにおけるアニメーションの種類

Ionicでは、画面遷移時にアニメーション効果が発生する。このアニメーション効果にはMaterial Design風の効果とiOS風の効果の2種類があり、さらに、前進用と後退用のアニメーションが用意されている。

<table>
  <tr>
    <th rowspan="2">animation　　　　</th>
    <th colspan="2">direction</th>
  </tr>
  <tr>
    <td>'forward'</td>
    <td>'back'</td>
  </tr>
  <tr>
    <td>'md'</td>
    <td><img alt="これはmdモードでのforwardアニメーションです。" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48238/7054c1e1-0e36-fdf7-85ca-cae8bc34e2e8.gif"/></td>
    <td><img alt="これはmdモードでのbackアニメーションです。" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48238/e76020fa-ad62-dc19-f343-ad0ab2440366.gif"/></td>
  </tr>
  <tr>
    <td>'ios'</td>
    <td><img alt="これはiOSモードでのforwardアニメーションです。" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48238/9d54251b-b628-578b-b58d-3d5118377d90.gif"/></td>
    <td><img alt="これはiOSモードでのbackアニメーションです。" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48238/77f426bc-6e8a-246e-2e81-fa12c4745506.gif"/>
</td>
  </tr>
</table>

[Ionic3の場合は、この4パターンのアニメーションを`NavController`のメソッドから制御することができた。](https://ionicframework.com/docs/v3/api/navigation/NavController/#push)
※オプションを省略した場合は、プラットフォームに対応して`animation`が適用される。

<table>
  <tr>
    <th rowspan="2">animation　　　　</th>
    <th colspan="2">direction</th>
  </tr>
  <tr>
    <td>'forward'</td>
    <td>'back'</td>
  </tr>
  <tr>
    <td>'md'</td>
    <td>navController.push(NextComponet, {}, { animation: 'md', direction: 'forward' })</td>
    <td>navController.push(NextComponent, {}, { animation: 'md', direction: 'back' })</td>
  </tr>
  <tr>
    <td>'ios'</td>
    <td>navController.push(NextComponet, {}, { animation: 'ios', direction: 'forward' })</td>
    <td>navController.push(NextComponent, {}, { animation: 'ios', direction: 'back' })</td>
  </tr>
</table>

## Ionic4での問題

しかし、[Ionic4の`NavController`では`animation`を指定するオプションが削除され、`forward`と`back`の区別しかつけられなくなってしまった](https://github.com/ionic-team/ionic/blob/a7a2fc59bf6bcb9641be665dced7108802879a43/angular/src/providers/nav-controller.ts#L12)。

<table>
  <tr>
    <th rowspan="2">animation　　　　</th>
    <th colspan="2">direction</th>
  </tr>
  <tr>
    <td>'forward'</td>
    <td>'back'</td>
  </tr>
  <tr>
    <td>N/A</td>
    <td>navController.navigateForward('nextPath', {}, { direction: 'forward' })</td>
    <td>navController.navigateForward('nextPath', {}, { direction: 'back' })</td>
  </tr>
</table>

2019年5月現在、ドキュメントで説明されている限りは、遷移ごとに`animation`を切り替える方法は提供されていない。このため、**iOSテーマでは常に横方向、MDテーマでは常に上下のアニメーションが強制される**ことになる。

## デザイン上は使い分けたい

この仕様は、アニメーションを使って画面間の関係を表現したいときに単純に不便だ。**UIデザイン的には、左右のアニメーションと上下のアニメーションは明確に意味が異なっているし、プラットフォームごとにどちらかへ統一するべき挙動であるとも思えない**。

参考：[アプリに最適なアニメーション遷移とスピードを考えてみよう！](https://blogs.adobe.com/japan/web-xd-ui-transition-easing/)
- [スライド](https://blogs.adobe.com/japan/web-xd-ui-transition-easing/#slide)
- [スライドアップ／ダウン](https://blogs.adobe.com/japan/web-xd-ui-transition-easing/#slideup)

# 暫定的な解決策

**プラットフォーム／テーマに関係なく、Ionic3のように都度画面遷移の方向を変える方法**がないか色々と試行錯誤した方法を以下へ記載する。

**ただし、ドキュメントに記載がなかったり内部プロパティを使っているものは、今後の仕様変更で削除される可能性があることにも注意したい。**

## `<ion-router-outlet>`に`mode`属性を指定する

今のところドキュメントに記載されていないが、`<ion-router-outlet mode="ios">`のように`mode`属性を指定すると、画面遷移のデフォルトの挙動をプラットフォームから独立して変更させることができる。

つまり、アプリ全体を通じて左右アニメーションが多い場合は`mode="ios"`、上下アニメーションが多い場合は`mode="md"`を指定すると都合が良くなる。

### `<ion-router-outlet>`の`mode`属性を動的に変化させる

さらに、この`mode`属性を動的に変化させた場合、そのとき付いている値に応じてアニメーションの種類も変化する。

```typescript:app.component.ts
@Components({
  selector: 'app-route',
  template: '<ion-app><ion-router-outlet [attr.mode]="mode"></ion-router-outlet></ion-app>'
})
export class AppComponent {
    public mode: string = 'ios';
    constructor(...){...}
}
```
```typescript:child.component.ts
@Components({
  selector: 'child-component',
  template: `
    <ion-button (click)="navForward('md')">MD Mode</ion-button>
    <ion-button (click)="navForward('ios')">iOS Mode</ion-button>
  `
})
export class ChildComponent {
    constructor(
     private navCtrl: NavController,
     @Optional() @Host() @SkipSelf() private app: AppComponent,
    ){...}

    public navForward(mode: string): void {
      this.app.mode = mode;
      this.navCtrl.navigateForward('next-page');
    }
}
```

### なぜ`mode`属性が効いているのか

実装を見ると、[modeプロパティ](https://github.com/ionic-team/ionic/blob/59bd823d05980ec31a91cad0839a999b9af5b41b/core/src/components/router-outlet/route-outlet.tsx#L27)が画面遷移を実行するときのパラメータの一部になっている。
**しかし、`@internal`コメントもあるため将来的に削除される可能性が否定できない。**

## `<ion-nav>`のメソッドオプションを使う

Ionic4ではルーティングの仕様が変わり、推奨では`<ion-router-outlet>`でのルーティングが推奨されている。
一方で、Ionic3にもあった`<ion-nav>`での画面遷移も引き続き残されている。
（ただし、Ionic3と同じ仕様ではない。）

これを使うことで、遷移の度に`mode`を指定することもできる。

https://ionicframework.com/docs/api/nav

**とはいえ、Angular Routerでの実装に慣れているとかなり使いにくい。**

## 完全な解決策は見つからず…

結局、完全・安全な解決策は見つかっていない。もし良い方法が見つかった場合には更新したい。