Ionic3でアプリを作る際のLazy Loadingへの対応

最近(2018年10月)、Ionic3でAndroidアプリを作って、Play Storeで公開しました。

そこに至るにはいろいろと罠がありましたので、その記録を思い出せるうちに書いて残しておこうと思います。

困ったり解決したりする度に[ツイートしてた](https://twitter.com/agajo_tech)からいけるはず……！！

いくつかの記事にわけて、シリーズとしてまとめておきます。

なお、なんとかリリースできたアプリはこちら。あなたの日本語入力速度を測定します。→[フリックとローマジ 〜日本語入力スピード測定〜](https://play.google.com/store/apps/details?id=oka_ryunoske.work.flickromaji.app)

#シリーズ一覧

 - [小さめの奴をまとめて](https://qiita.com/agajo/items/dee6c5c994fe0e5ef001)
    - Ionicアプリにおける、色の指定のしかた
    - アプリのIDを初期値にしたまま端末に複数インストールすると上書きされる
    - iconとsplashの生成
    - android実機デバッグでエラーメッセージを見る
    - chromeでinspectタブを開いていると、端末ビルド時にno device error
    - play-services-ads v17 でAndroidManifest.xmlのmeta-data強制
 - [複数のファイルにまたがるPromiseの使い方](https://qiita.com/agajo/items/c466a4cdc3930dd43206)
    - IonicのNavControllerはProviderからは使えない
 - [プライバシーポリシー](https://qiita.com/agajo/items/e886afb7f5408141d1fc)
 - [admob-freeプラグインでのeventsオブジェクトの扱い](https://qiita.com/agajo/items/fd1e61e3325552aaefaf)
 - [firebaseプラグイン導入時のGoogle Play Services libraryのバージョン調整](https://qiita.com/agajo/items/8106eb0883d2d3c82add)
 - Lazy Loading への対応(この記事)
    - ngIf、ngForが動かない時


この記事では、Lazy Loadingについて扱いますが、PageとComponentの場合の話だけします。PipeやDirectiveの場合については触れませんが、基本的にComponentの場合と同じです。

#Lazy Loading????

Lazy Loadingとは、アプリ内で使うモジュールを、必要な分だけ随時読み込んでいく仕組みのことです。

最初に全部いっぺんに読み込まないので、その分起動が早くなるメリットがあります。

詳しくはこちら。

[Ionic and Lazy Loading Pt 1  |  The Official Ionic Blog](https://blog.ionicframework.com/ionic-and-lazy-loading-pt-1/)
[IonicでLazy Loadingに対応する - Qiita](https://qiita.com/akisx/items/a64010d55d746837cb1c)


#Lazy Loadingの時とそうでない時のモジュール宣言の違い

AngularやIonicで使用するモジュールは、どこかでモジュールとして宣言されていなければいけません。PageとかComponentとかPipeとかDirectiveとか、全部モジュールです。

具体的には、`なんとか.module.ts`というファイルの中で、`@NgModule`デコレータを使って

```typescript:hoge.module.ts
import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { HogePage } from './hoge';

@NgModule({
  declarations: [//declaration(「宣言」という意味の英単語)
    HogePage,//←宣言の中に「HogePage」を加える
  ],
  imports: [
    IonicPageModule.forChild(HogePage),
  ],
})
export class HogePageModule {}
```

という感じで宣言します。

この宣言を、`hoge.module.ts`で独自にやるか、もっと親玉の`app.module.ts`でたくさんまとめて宣言するか、という違いが、Lazy Loadingかそうでないかの違いになります。

この宣言をごちゃ混ぜにして、一つのモジュールを`app.module.ts`とそれぞれの`module.ts`の２箇所で宣言したりすると、エラーになります。

しかも、`ionic cordova run android --device`の時はエラーにならないのに、`ionic cordova run --prod --release`の段階になって初めてエラーになるので、気づきにくく、タチが悪いです。

```:エラーメッセージの例
Type Hoge in hoge.ts is part of the declarations of 2 modules: AppModule in src/app/app.module.ts and ComponentsModule in src/components/components.module.ts
```

##Pageのモジュール宣言
###Lazy Loading
それぞれのページについて、`module.ts`ファイルがあり、そこで宣言する。
###いっぺんに読み込む
`app.module.ts`で宣言

##Componentのモジュール宣言
###Lazy Loading
それぞれのコンポーネントについて`module.ts`で宣言するか、Component用の共有モジュール`components.module.ts`でコンポーネントたちだけ一括で宣言する

###いっぺんに読み込む
`app.module.ts`で宣言

###例

以下に、Component用の共有モジュールの例をあげておきます。

```typescript:components.module.ts
import { NgModule } from '@angular/core';
import { HogeComponent } from './hoge/hoge.component';
import { FugaComponent } from './fuga/fuga.component';
import { FooBarComponent } from './foo-bar/foo-bar.component';
import { CommonModule } from '@angular/common';//これについては後で解説します

@NgModule({
  declarations: [HogeComponent,
    FugaComponent,
    FooBarComponent],
  imports: [CommonModule],//これについては後で解説します
  exports: [HogeComponent,
    FugaComponent,
    FooBarComponent]
})
export class ComponentsModule {}
```



#Lazy Loadingの時とそうでない時の、ComponentやPageの呼び出し方の違い

通常の場合、ComponentやPageを呼び出す時には、該当するものをファイルの冒頭でimportして、コード内で使います。

が、Lazy Loadingの場合は、importしようにもまだモジュールが読み込まれていないため、importに失敗します。

なので、そもそもimport文も書かず、コード内で使用する時には、**文字列で**指定します。

文字列でいいの！？と思ってしまいますが、それでうまくやってくれます。すごいですね。

```typescript:通常読み込みの例
import { HogePage } from '..hoge/hoge';
...
  this.navCtrl.setRoot(HogePage);
...
```

```typescript:LazyLoadingの例
//import文は不要
...
  this.navCtrl.setRoot("HogePage");
...
```



#IonicのLazy Loading

Ionic3では、`ionic generate`コマンドでpageやcomponentをgenerateすると、Lazy Loadingに対応した形でモジュール宣言が出力されます。

なので、Angularチュートリアルで勉強したように`app.module.ts`に改めて宣言を書き加えると、二重宣言でエラーになります。

結局何もしなくていいってことですね。読み込む時にimport文を書かず、呼び出しを文字列にすることだけ気をつければOK。

Componentについては、個別に`module.ts`が用意されるのではなく、コンポーネント用共有モジュール`components.module.ts`が用意されます。

#ngIf、ngForが動かない時

詳しくはこちらに書いてあります。

https://angular.io/guide/frequent-ngmodules#browsermodule-and-commonmodule

要約すると、Lazy Loadingで読み込むモジュールの中で`*ngFor`や`*ngIf`を使いたい場合は、そのモジュールの`module.ts`で`CommonModule`をインポートせよ、ということです。

Componentを共用モジュールで宣言している場合は、その共用モジュールの`module.ts`でそのimportを行うことになります。上記のコード例を再掲しておきますね。

```typescript:components.module.ts
import { NgModule } from '@angular/core';
import { HogeComponent } from './hoge/hoge.component';
import { FugaComponent } from './fuga/fuga.component';
import { FooBarComponent } from './foo-bar/foo-bar.component';
import { CommonModule } from '@angular/common';//ここと

@NgModule({
  declarations: [HogeComponent,
    FugaComponent,
    FooBarComponent],
  imports: [CommonModule],//ここです。
  exports: [HogeComponent,
    FugaComponent,
    FooBarComponent]
})
export class ComponentsModule {}
```

#おわりに

この記事は以上です。同時に、このシリーズも以上です。

次はiOS版を作っていくことになるかな〜。