Ionic v4 (@ionic/angular)でのルーティングについて

この記事は、[Ionic Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ionicframework)の14日目の記事です。

# はじめに

Ionic v4ではページの遷移の方法が変わり、各フレームワーク用のルーターを使えるようになります。

この記事では利用者の多いと思われる Angular Router でのルーティングについて解説します。

# 基本

ページ遷移の元となる部分に`<ion-router-outlet>`を設置します。このコンポーネントは`@ionic/angular`専用です。Angularの`<router-outlet>`と同等の機能に加え、画面遷移アニメーションの処理などが追加されています。

```html:app.component.html
<ion-app>
  <ion-router-outlet></ion-router-outlet>
</ion-app>
```

ルーティングは下の様に、各`path`に対応する`component`を設定していきます。

```ts:app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './home/home.component';
import { UserListComponent } from './user-list/user-list.component';
import { UserDetailComponent } from './user-detail/user-detail.component';

const routes: Routes = [
  { path: '/', component: HomeComponent },
  { path: '/users', component: UserListComponent },
  { path: '/users/:id', component: UserDetailComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}
```

あとはこのモジュールを`AppModule`にインポートすればOKです。

ページを遷移するときは、

- `routerLink`ディレクティブ
- `Router`サービス
- `NavController`サービス（Ionic独自）

のいずれかを使用します。

```html:page1.component.html
<ion-header>
  <ion-toolbar>
    <ion-title>Page1</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>
  <ion-button routerLink="/page2" routerDirection="forward">page2</ion-button>
  <ion-button (click)="gotoPage2ByRouter()">page2</ion-button>
  <ion-button (click)="gotoPage2ByNavCtrl()">page2</ion-button>
</ion-content>
```

```ts:page1.component.ts
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { NavController } from '@ionic/angular';

@Component({
  selector: 'app-page1',
  templateUrl: 'page1.component.html',
  styleUrls: ['page1.component.scss'],
})
export class Page1Component {
  constructor(private router: Router, private navCtrl: NavController) {}

  // AngularのRouterで遷移
  gotoPage2ByRouter() {
    this.router.navigateByUrl('/page2');
    //this.router.navigate(['/page2']);
  }

  // IonicのNavControllerで遷移
  gotoPage2ByNavCtrl() {
    this.navCtrl.navigateForward('/page2');
  }
}
```

`/page2/:id`などURLのパスにパラメータを渡す場合は↓のように書きます。

```ts
// AngularのRouterで遷移
gotoPage2ByRouter() {
  this.router.navigateByUrl(`/page2/${id}`);
  //this.router.navigate(['/page2', id]);
}

// IonicのNavControllerで遷移
gotoPage2ByNavCtrl() {
  this.navCtrl.navigateForward(`/page2/${id}`);
}
```

`routerLink`ディレクティブの`routerDirection`プロパティは`@ionic/angular`独自のもので、遷移時のアニメーションを指定できます。

```html
<ion-button routerLink="/page2" routerDirection="forward">page2</ion-button>
```

なお、以下の値が設定可能です。

- forward（進む:デフォルト）
- back（戻る）
- root（差し替え, アニメーションなし）
- auto（自動）

遷移先のページで「戻る」ボタンを実装するときは`ion-back-button`を使いましょう。

```html:page2.component.html
<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button text="戻る" defaultHref="/page1"></ion-back-button>
    </ion-buttons>
    <ion-title>Page2</ion-title>
  </ion-toolbar>
</ion-header>
```

また、`/users/:id`のようなパラメータを取得する場合は、`ActivatedRoute`を使用します。

```ts:page2.component.ts
@Component({
  selector: 'app-page2',
  templateUrl: 'page2.component.html',
  styleUrls: ['page2.component.scss'],
})
export class Page2Component implements OnInit {

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    const id = this.route.snapshot.paramMap.get('id');
  }
}
```

## 応用

## モジュールのLazy Loading

モジュールのLazy LoadingはWebアプリとして動かす場合は有用です。Ionic v3では`@IonicPage`デコレータを使ってLazy Loadingしていましたが、v4からはAngular Routerの機能を使って

```ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  { path: '/', loadChildren: './home/home.module#HomeModule' },
  { path: '/users', loadChildren: './user-list/user-list.module#UserListModule' },
  { path: '/users/:id', loadChildren: './user-detail/user-detail.module#UserDetailModule' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}
```

の様に書くとモジュールをLazy Loadingしてくれます。

## モジュールのPreloading

Cordovaで動かす場合は既に必要なデータが端末にインストールされているため（ページ遷移をスムーズに行うためにも）事前に読み込みを完了していることが望ましいです。

かといって、せっかくLazy Loading対応させたルーティング設定を今から書き直すのもちょっと...という場合は`RouterModule.forRoot()`で`PreloadAllModules`を指定しましょう。

```ts
RouterModule.forRoot({ routes, preloadingStrategy: PreloadAllModules });
```

### メニュー

`ion-menu`でメニュー画面を作り、コンテンツを表示する部分に`ion-router-outlet`を使います。

```html
<ion-menu contentId="main">
  <ion-header>
    <ion-toolbar>
      <ion-title>メニュー</ion-title>
    </ion-toolbar>
  </ion-header>
  <ion-content>
    <ion-list>
      <ion-menu-toggle>
        <ion-item>メニュー1</ion-item>
        <ion-item>メニュー2</ion-item>
      </ion-menu-toggle>
    </ion-list>
  </ion-content>
</ion-menu>
<ion-router-outlet id="main"></ion-router-outlet>
```

`ion-menu`を使う場合は`ion-router-outlet`に`main`属性または`id`の指定が必要です。
https://qiita.com/puku0x/items/a278e31d90164b535299

### タブ

v4-beta.18から各タブがLazy Loadingできるようになりました。ルーティングの書き方は前述したLazy Loadingのものと同じで、v4-beta.17以前の名前付き`ion-router-outlet`と比べてシンプルになっています。

```html
<ion-tabs>
  <ion-tab-bar slot="bottom">

    <ion-tab-button tab="home">
      <ion-icon name="home"></ion-icon>
      <ion-label>Home</ion-label>
    </ion-tab-button>

    <ion-tab-button tab="about">
      <ion-icon name="information-circle"></ion-icon>
      <ion-label>About</ion-label>
    </ion-tab-button>

    <ion-tab-button tab="contact">
      <ion-icon name="contacts"></ion-icon>
      <ion-label>Contact</ion-label>
    </ion-tab-button>

  </ion-tab-bar>
</ion-tabs>
```

↑HTML側は`ion-tab-button`の`tab`属性に各タブのパスを書くだけです。

各タブのルーティングを`children`に書いていきます。`redirectTo`の部分は必須です。

```ts
const routes: Routes = [
  {
    path: '',
    redirectTo: '/tabs/home',
    pathMatch: 'full'
  },
  {
    path: 'tabs',
    component: TabsComponent,
    children: [
      {
        path: 'home',
        children: [
          {
            path: '',
            loadChildren: './home/home.module#HomeModule'
          }
        ]
      },
      ...
    ]
  }
];
```

タブ内でページ遷移する場合、v4-beta.17以前では親モジュール（`<ion-tabs>`のあるところ）にルーティングを書いていましたが、v4-beta.18以降は各タブのモジュールに書けばよくなったのでわかりやすくなりました。

```ts:contact-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { ContactComponent } from './contact.component';

const routes: Routes = [
  {
    path: '',
    component: ContactComponent 
  },
  {
    path: 'detail',
    loadChildren: './contact-detail/contact-detail.module#ContactDetailModule'
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ContactRoutingModule {}
```

# 今後変更されそうな部分

## ページ遷移時の引数

Angular v7.2でルーターによる遷移時に任意のデータを渡せるようになるそうです。

https://github.com/angular/angular/pull/27198

# おわりに

v3までのPush/Popで遷移する方式と違い、v4は自分でルーティングを組む必要があるので敷居がちょっと上がった感じはありますが、Webアプリからスマホアプリ、PWAまでカバーできるのでIonicは魅力的なフレームワークです。

普段からSPA開発に慣れていて、スマホアプリへの展開も考えている場合などには良い選択肢になると思います。

AngularとIonic両方追いかけるのは大変ですが、是非とも使いこなせるようになりたいですね。