Ionic4での画面遷移の種類と使い分け

Ionicには画面遷移を実現する方法が複数用意されていて、Ionic3と4で似ている部分や変わった部分もあります。自分の場合、仕事の都合でIonic3に慣れ始めてからIonic4へ乗り換えたため、違いや使い分けが分からず実装に一貫性がなくなってしまうこともありました。(作り直したい……。)

この記事では、Ionic4の画面遷移まわりの機能の種類や、どういった意図でそれらを使い分けると良いのか、現時点での考えを記載します。

## `ion-router-outlet`と`NavController`

- [ion-router-outlet - Ionic Documentation](https://ionicframework.com/docs/api/router-outlet)
- [Angular Navigation - Ionic Documentation](https://ionicframework.com/docs/angular/navigation)
- [Using Angular Routing with Ionic 4 | joshmorony - Learn Ionic & Build Mobile Apps with Web Tech](https://www.joshmorony.com/using-angular-routing-with-ionic-4/)
- [ionic/nav-controller.ts at master · ionic-team/ionic](https://github.com/ionic-team/ionic/blob/master/angular/src/providers/nav-controller.ts)

Angular版のIonicで推奨されている画面遷移の機能です。

Angular Routingと統合されており、URLベースの画面遷移を提供します。使い方はAngularの`router-outlet`とほぼ同じですが、Ionic独自の拡張がされています。

HTML側からはa要素の`routerLink`属性、TypeScript側からは`NavController`クラスを使って遷移を制御します。

この`NavController`はIonic3の`NavController`とは別物で、メソッド名や引数も変わっています。

### ナビゲーションスタック

`ion-router-outlet`での画面遷移にはIonic独自のナビゲーションスタックの概念があります。この概念はIonic3と4でほぼ変わりません。

ナビゲーションスタックは画面遷移の履歴をキャッシュする仕組みのようなもので、ナビゲーションスタックに追加された画面は次の画面に遷移してもコンポーネントのインスタンスが維持されます。この挙動は通常のAngularとは違うので注意が必要です。

- [IonicはナビゲーションしてもngOnDestoryが呼ばれない！ - Qiita](https://qiita.com/fusho-takahashi/items/232434fa7a3e17ffaf76)
- [Ionic 4 and the Lifecycle Hooks in Angular - Paul Stelzer - Medium](https://medium.com/@paulstelzer/ionic-4-and-the-lifecycle-hooks-4fe9eabb2864)


### 次の画面へ進む機能・使い分け

- `NavConrtroller`の`navigateForward()`で遷移する場合、ナビゲーションスタックに現在までの画面を残したまま次の画面コンポーネントを表示します。デフォルトでは次へ遷移するアニメーションが発生します。
    - `routerLink`に`routerDirection`を指定しない、又は`routerDirection="forward"`を指定した場合も同じ挙動になります。
- `NavConrtroller`の`navigateRoot()`で遷移する場合、これまでのナビゲーションスタックを破棄して、次の画面から新規のナビゲーションスタックを開始します。デフォルトではアニメーションは発生しません。
    - `routerLink`に`routerDirection="root"`を指定した場合も同じ挙動になります。

使い分けは、

- 一般的な画面遷移では`navigateFoward()`
- ナビゲーションスタックをリセットしたい場合は`navigateRoot()`

となります。一般的な`navigateForward()`は、次の画面に進んでもコンポーネントのインスタンスが残り続けることを必ず意識しましょう。

### 前の画面へ戻る機能・使い分け

前へ戻るためのメソッドでは、微妙に機能が違うものが複数提供されています。全てのメソッドで、デフォルトでは前へ遷移するアニメーションが発生します。

- `NavController`の`pop()`で遷移する場合は、ナビゲーションスタックの直前の画面へ戻り、今見えている画面はナビゲーションスタックから破棄されます。
- `NavConrtroller`の`navigateBack()`で遷移する場合は、引数で指定された画面がナビゲーションスタックに含まれている場合、それよりも後のナビゲーションスタックは破棄されます。
  - `routerLink`に`routerDirection="back"`を指定した場合も同じ挙動になります。
- `NavController`の`back()`で遷移する場合は、Angularの`Location.back()`と同じ挙動になります。

使い分けとしては以下のようになります。

- コンテクストに応じて前の画面に戻したい場合は`pop()`
- コンテクストに関係なく特定の画面に戻したい場合は`navigateBack()`
- ブラウザのバックボタンと同じ挙動で前に戻したい場合は`back()`

### パラメータの渡し方

Angular Routerと同じ方法で遷移先の画面へパラメータを渡せます。
https://angular.jp/guide/router

## `ion-modal`と`ModalController`

- [ion-modal - Ionic Documentation](https://ionicframework.com/docs/api/modal)
- [ion-modal-controller - Ionic Documentation](https://ionicframework.com/docs/api/modal-controller)

`ion-modal`は、見えている画面の上に別のコンポーネントを重ねて表示する機能で、`ion-modal`の生成・表示・非表示を制御するのが`ModalController`です。

特にスマートフォンサイズの場合、MaterialDesignテーマでは`ion-router-outlet`による遷移と見た目としてはほぼ同じ効果が起こります。しかし、ルーティングによる画面遷移と意味合いは異なります。

### ルーティングによる画面遷移との違い・使い分け

`ion-router-outlet`を使ったルーティングによる画面遷移ではURLが変化します。また、遷移中のアニメーションが終了した後は、遷移前後の画面を同時に表示することが想定されていません。

一方`ion-modal`でコンポーネントを表示する場合は、既存の画面表示とURLを維持したまま、単純にその上へ新しいコンポーネントを重ねて表示します。表示の制御もルーティングとは独立しており、また`ion-modal`単体で複数のコンポーネントを切り替えて表示する機能はありません。

そのため、`ion-router-outlet`との使い分けは次のようになります。

- `ion-modal`を使う場合
  - コンポーネントを重ねて同時に表示したい
      - フォームの入力を補助する半モーダルを出したい
          - 参考： [Ionicで半モーダルを作成する - Qiita](https://qiita.com/scrpgil/items/910f60ef23fd393996c0)
      - タブレット用アプリで一次的に子画面的なものを出したい
   - ルーティングへ影響を与えず、一次的に別の画面を表示したい
- それ以外は`ion-router-outlet`を使う

### パラメータの渡し方

```typescript:parent.component.ts
...
public async createModal(): void {
  const modal = await this.modalCtrl.create({
    component: ChildComponent,
    componentProps: { id: this.id }, // *A
  });
  await modal.present();
  const { data } = await modal.onDidDismiss(); // *b
  console.log(data.result);
}
```

```typescript:child.component.ts
@Component({...})
export class ChildComponent {
  @Input() public id: string; // *a
  constructor(
    private modalCtrl: ModalController
  ) {
  }

  public close(): void {
    this.modalCtrl.dismiss({ result: 'success' }); // *B
  }
}
```

#### モーダルの生成元からモーダル内部へのデータの引き渡し

`componentProps`というキーでオブジェクトを渡すと、モーダル内部の同名のプロパティへ自動的に引き渡されます。(*A, *a部)

#### モーダル内部からモーダル生成元へのデータの引き渡し

モーダル内部から`ModalController`の`dismiss()`へ引数として渡したデータを、モーダル生成側のコンポーネントからは`onDidDismiss()`か`onWillDissmiss()`で非同期で受け取れます。(*B, *b部)

## `ion-nav`

- [ion-nav - Ionic Documentation](https://ionicframework.com/docs/api/nav)

ナビゲーションスタックを持ち、`ion-router-outlet`と似た画面遷移の機能を提供します。しかし、ルーターとしての機能はなくルーティングには影響しません。

Ionic3の`ion-nav`や`NavController`とよく似ていますが、Ionic4のAngular版であえて`ion-nav`を使うべき状況は限定的だと思われます。

`ion-modal`の内部で画面遷移をさせたい場合は`ion-nav`を組み合わせて使うことになります。

// ion-navの使い方についてはあとで追記したいです…。

## 終わりに

表面的な挙動としては似ているところもある`ion-router-outlet`、`ion-modal`、`ion-nav`ですが、全て実装方法が違うので、ある実装のために作ったコンポーネントを別の実装に使いまわそうとしてもできなかったりします。

特別な要件がなければ`ion-router-outlet`での画面遷移に寄せるべきでしょう。

ドキュメントだけでは分からない部分も多いので、ググっても見つからない部分はGitHubのソースコードを読みに行くのが良さそうです。