ionicでアプリ開発入門 新しいページの追加をしてみよう

2018-10-02

では、サクッと新しいページを作ってみましょう。
サイトを作るのもアプリを作るのもチュートリアルを使いまわすことはできないですからね。

ionicでページを追加してみよう

ページを追加するにはhtmlやcssなどを新規作成する必要は有りません。
コンソールで1行書くだけです。

ionic g

gはgenerateのgです
ionic generateと打っても大丈夫です。

ところでvscodeでもコマンド打てるって知ってました?
英語表記で申し訳ないですが、
vscode でターミナルを起動
メニューバーのTerminalから、New Terminalで下の方に新しくターミナルが表示されるはず。
邪魔なら×ボタンで消してください。
大体のことを操作できるのでターミナルとvscodeとの行き来が無くなるので結構便利ですよ。:)

コマンドを打つとターミナルで選択画面が表示されます。
ionic gコマンド

選択項目が出てきますが、ページを追加したいので
pageを選択
Enterを押して、ページの名前を決めてください。
新しいページの名前を決める

完了するとアナウンスが流れます。
完了

ね、簡単でしょ?

ファイルを確認しよう

さて、コマンドで

generated a page named testMood!

テストという名前のページを生成しました気分!
うん、modeとmoodをナチュラルに間違えました。
テストの気分なんです。

generated a pageなのでページが作成されたようですね。
vscodeでファイルを見ていきましょう。

ファイルがどこに作られるかと言うと、srcのpageの下へ作られます。
pageフォルダの下
基本的には全部pageの下です。
ionicを使わないでhtmlを作るとしたら、エイリアス等を使わなければ
sunababox.com/page/testMood.htmlとかになるんでしょうか。

vscodeでpageの中を見ると勝手に作られているファイルがあります。
/page/test-mood/
test-modeディレクトリが作られていてその中にtsとcss、htmlまでセットで入ってます。
かなり楽ですね。

ionic serveでブラウザを立ち上げて確認したいところですが、ファイルを作っただけでまだリンクは付いていません。
でもせっかくなので確認してみると挙動が覚えやすいですよ。

ionic gで作ったファイルをメニューに登録しよう

ではメニューにリンクをつけていきましょう。
前の記事を見ていただきたいのですが、メニューを司るのはどのファイルか
hello-ionicを表示するまでの道のりをおさらい
こちらの図で見ると
<ion-menu [content]="content">がメニュー部分なので
app.html
app.component.ts辺りに該当しますね。

先にapp.htmlを見ていきます。

<ion-menu [content]="content">
<ion-header>
<ion-toolbar>
<ion-title>Pages</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<button ion-item *ngFor="let p of pages" (click)="openPage(p)">
{{p.title}}
</button>
</ion-list>
</ion-content>
</ion-menu>

今回見るべきところは
<ion-list>
<button ion-item *ngFor="let p of pages" (click)="openPage(p)">
{{p.title}}
</button>
</ion-list>
ここですかね。
<ion-list>と<ion-item>はセットで出てくることが多いです。というかほとんどセットで使います。
イメージとしては<ul>と<li>でしょうか
ion-listがliのリストとして表示されます。
ではion-listが何をしているかと言うと、
*ngFor="let p of pages"

*ngForで繰り返しなさい、という命令
それ以降の""の中に命令の内容を書いています。
(*ngFore)繰り返しなさい"let p(に) (of)の (pages)を"
letとは変数に当ります。
変数とは、、、と書くと別のお話が始まってしまうので・・・

プログラミング言語における「値を入れておく箱」のこと

とりあえず引用しておきます。いずれ変数とはなんぞやを雑記に書かなければ。。。

簡単に書くと、pagesで定義されているものをpという値を入れておく箱(カラーボックスみたいなやつ)に取り出して表示する。
{{p.title}}の部分は、let pで定義されているtitleという商品名を表示させておいてね。
となるのでしょうか。
そして、ngForなのでそれを繰り返しなさいという命令です。
ちなみに、なんでpなん?と疑問に思う事があるかもしれません。
pタグかな?とか思いましたが、多分これpagesの省略です。
なので、別にpじゃなくて良いんです。bでもpageでも
その代わり、letの後だけじゃなく残りもすべて書き換えてあげる必要があります。

どうせ壊しても直せばいいし、一度やってみると覚えが早いです。
どんどんぶっ壊しましょう。

例としてはこんな感じ

<button ion-item *ngFor="let peeejidesu of pages" (click)="openPage(peeejidesu)">
{{peeejidesu.title}}

click="open~~"の事はまた後でね。

とりあえずここでは説明の為にpに戻しておきます。

ではapp.htmlを制御しているapp.component.tsを見ていきます。

app.component.tsはpagesがある

vscodeでapp.component.tsを開いてpagesという配列を探しましょう。

importとコメントアウトを除いて、2件表示されます。

  rootPage = HelloIonicPage;
pages: Array<{title: string, component: any}>;
constructor(
public platform: Platform,
public menu: MenuController,
public statusBar: StatusBar,
public splashScreen: SplashScreen
) {
this.initializeApp();
// set our app's pages *これはいらない
this.pages = [
{ title: 'Hello Ionic', component: HelloIonicPage },
{ title: 'My First List', component: ListPage }
];
}

上から見ていきましょう
pages: Array<{title: string, component: any}>;

型の定義

ここで、pagesという配列を定義しています。
定義しないと型は使えません。phpとかだといきなり使う事が出来るんですが、javascriptではなんでも定義しないと使えないんです。
なのでpagesっての使いますよ、という定義を行い{}の中で、何を入れても良いかを決めています。

ここで出てきました、title
{{p.title}}のtitleはここで決定されてたんですね。
なので、p.title2とか書いてもなにも出てきやせんのよね。

ちなみに、stringは文字列の事を指します。
{titleという型を作り、その中にはstring(文字列)を入れることをここに誓う}
見たいになります。
ちなみにcomponent:anyのanyは何でも入ります。
訳すと"どれか"になるんですが、まあ何が入るか未定の場合とかで使っておけば良いんじゃない?

ここら辺はいずれkwskする必要がありますね。
配列とはなんぞや、みたいな。

さて、配列は容易したけど中身は?
と言うと、もう一つのpagesに書かれています。

    this.pages = [
{ title: 'Hello Ionic', component: HelloIonicPage },
{ title: 'My First List', component: ListPage }
];

this.pages
this!
このpages
この?どの?
thisも厄介ですね、呼び出し方によって出力が変わってくるので
私の方でまとめたら追記するという事で、
今は別のサイトから引用させていただきましょう。
多分一番わかりやすいのはここ。勝手に書いてごめんなさい。

thisの値は、呼び出される際のコンテクストに依存して決まります。

コンストラクタとメソッドとか横文字を色々説明せねばいけなくなるので省きますが
今は、
this.pagesのthisは、
pages: Array<{title: string, component: any}>;
このpagesだと思ってください。
その方がすんなり頭に入ってきます。
そのうちthis.を使ってれば感覚的にわかってくる人もいるかも。
考えるな、感じろ
と偉人も言ってました。

ではthis.pageの中身を見ていきましょうか
this.pages =(の中に)[
これを入れるよ
{ title: 'Hello Ionic’, component: HelloIonicPage },
{ title: 'My First List’, component: ListPage }
];
という内容で伝わるかな。
中に2つ入ってます。
pagesの中にある{titleに’Hello Ionic’を入れるよ component:にはHelloIonicPageをいれるよ}
pagesの中にある{titleに’My First List’を入れるよ component:にはListPageをいれるよ}
書かれてるのはこれだけです。
titleの中身は、app.htmlに書かれてた{{p.title}}の部分に表示されるテキストが入ってます。
htmlでいえば

<a href="">title</a>

こんな感じでしょうか。

ではここでapp.htmlへ戻ってみます。
app.htmlの*ngFor="let p of pages"で、pagesの中身がある分繰り返し、pを使って表示する。
pagesには2項目
p.titleにはHello IonicとMy First Listが入ってるので2回繰り返すぜ。
という結果がブラウザで表示できるはず。
ちょっと眠くてややこしくなってきた。

混乱してきそうなので無理やり話しを進めますね。

ここでやっとこブログの本題ですが、
{ title: 'Hello Ionic’, component: HelloIonicPage },
{ title: 'My First List’, component: ListPage }
これ、もう一行増やせばメニュー増えます。
環境があるなら一緒にやってみましょう
app.component.tsを開いて、this.pagesの下

{ title: 'Hello Ionic', component: HelloIonicPage },
{ title: 'My First List', component: ListPage }

これを、
{ title: 'Hello Ionic', component: HelloIonicPage },
{ title: 'My First List', component: ListPage },
{ title: '新しいページ', component: ListPage }

こうじゃ
追加したのは下の1行と、2行目の文末に,をつけました。
{},括弧カンマで1つの区切りと認識させています。

では更新してみましょう。
新しいリンクを追加

できてるー。
でも、飛び先がListPageですね。
そもそも飛び先はどこで決めてるの?

そこで、すっ飛ばしてきた
(click)="openPage(p)">
が出てくるわけですよ。

(click)="openPage()"でページ遷移しよう

openPageとはなんぞや。
javascriptで説明するとonClick
誤解が生まれそうだけど、htmlタグで説明すると
<a href="このぶぶん"></a>

でーはー、解説始めます。
openPageが何をしているかと言うと
app.htmlには動作が無くて、app.component.tsの方に書いてます。

  openPage(page) {
// close the menu when clicking a link from the menu
this.menu.close();
// navigate to the new page if it is not the current page
this.nav.setRoot(page.component);
}

この部分ですね。
ん?
app.htmlの方は
openPage(p)

app.component.tsの方は
openPage(page)
括弧の中違いませんか?

これは間違いではありません。動いてますし
app.htmlの方は、*ngForで定義しているpを使っています。

app.component.tsの方は、独自に設定したものとしてpageを使っています。
試しに、app.component.tsの方のopenPage(page)をopenPage(pagedesu)に変更してみましょう。
その際には、下の方にあるthis.nav.setRoot(page.component);
ここもthis.nav.setRoot(pagedesu.component);
へ変更します。
これでも動いちゃうんですよね。

だったら合わせればよいじゃんってことになるんですが
合わせても動きます。
動くけど予想だと拡張していくにつれて不具合起きる気がする。

app.htmlの方のopenPageは何なのよ

app.htmlの方は送り側です。
(click)="openPage(p)">
クリックした際にopenPageという処理を行いなさい。その際に、(p)という情報を持っていくのよ。

app.component.tsの方は何なのよ

app.component.tsの方は受け取り側です。
openPageの処理が来たら実行します。
その際に、app.htmlから送られた情報を(page)と言う値を使って受け取ります。
になるはず。

受け取り側なので何で受け取っても良いです。
ヤマトの箱で送られてきた中身を西濃の箱に詰め替えて使っても何ら問題は無い。
ただし、pとpageは同じものが入っています。

引き続きapp.component.tsの中身を見ていきましょう
openPage(page)の処理は{}に囲まれた部分です。

this.menu.close();
でたよ、this.
でもニュアンスでわかりますよね。
app.htmlのion-menuを閉じなさい。という処理です。

もう一行ありますね
this.nav.setRoot(page.component);
こっちもthis.だよ。
こちらもなんとなくわかります。
navはion-navのことですね。
ion-navの内容を書き換え(set)しなさい。中身はopenPage(page)で受け取ったpageの中のcomponentを使いなさい。
と言う処理です。

やってることはあまり難しくないですね。

ところで、無理にopenPageという処理(メソッド名?)を使う必要はなくて
たとえば両方のメソッドをopenpenPage()とかにしても動きます。
是非試してみてください。

ちなみにメソッドは方法や方程式という意味らしいですぜ。
業界的に良くつかわれる言葉なんですが私のように極端に英語が苦手だと一瞬何のことかわかりません。

ヤフー知恵袋のようなアジェンダをタスクとするメソッドは、ユーザーのスペックがコミットメントされない限りコンセンサスはエビデンスされない。
利用者のジャストアイデアに偏重するフィックスはシェアの偏りを招き、タイトなレスポンス・スペースにおいてシナジーとしてのブラッシュアップをペンディングしかねない。
まったく、デフォルトにナーヴァスなシーチキン巻きがモチベーションにおいて日常が担保されるのだ。
参考ヤフー知恵袋のようなアジェンダをタスクと
こんな感じ

何年か前にコンプライアンスって言葉がやたらと流行った時期ありましたよね。
もちろん今でも使われる言葉ですが、私はコンプライアンスって言葉で何件もの無理難題を回避した事があります。
「それはコンプライアンスに引っかかるんじゃないでしょうか」
具体的に何の誰の法令とは言ってませんが。

componentには何が入っているの?

さて、ここまで見てきて大体の人は予想できていると思います。
componentの中には切り替えるページの遷移先が入っているのではないか。
ざっつらいとです。
app.component.tsのthis.pagesを見なおしましょう。

{ title: 'Hello Ionic', component: HelloIonicPage },
{ title: 'My First List', component: ListPage },

componentにはそれぞれHelloIonicPageとListPageが入ってます。

じゃあ、これもimportしてるんじゃないでしょうか。

import { HelloIonicPage } from '../pages/hello-ionic/hello-ionic';
import { ListPage } from '../pages/list/list';

有りました。

では、新しく作ったページをimportしてやりましょう。

import { HelloIonicPage } from '../pages/hello-ionic/hello-ionic';
import { ListPage } from '../pages/list/list';
import { TestMoodPage } from '../pages/test-moode/test-mood';

こうです。

ionic gコマンドをつかってページを追加してやると
test-mood.tsが
勝手にclassを作ってくれており

export class TestMoodPage {

TestMoodPageと命名されています。

この命名規約ですが、頭文字が大文字、-ハイフンなどの区切り先頭が大文字、そして最後にPageをつけるキャメルケース記法で生成されます。
ぱっと見で何を意味するかわかりやすいので便利ですよね。
キャメルケース参考:変数/クラス名の命名規則に使われる記法の分類

なのでそれに沿ってimport{}の中はTestMoodPage
from"の中はtest-mood.tsまでのパスを書いてやります。

次に、
this.pages[]の中、追加した配列のcomponentを書き換えてやりましょう。

    this.pages = [
{ title: 'Hello Ionic', component: HelloIonicPage },
{ title: 'My First List', component: ListPage },
{ title: 'なんかかっこいいタイトル', component: TestMoodPage }
];

上記で一応test-mood.htmlの中身を表示できるのですが
実行してみると
エラーになるはずです。
エラー表示


ランタイムエラー
エラー:TestMoodPageのコンポーネントファクトリが見つかりませんでした。
エラー:TestMoodPageのコンポーネントファクトリが見つかりませんでした。
@ NgModule.entryComponentsに追加しましたか? noComponentFactoryError(http:// NgModule.entryComponents?

エラーが色々解決してくれます。

@NgModule.entryComponentsに追加?
どこそれ
こんな時はググれば大体解決しますよね。

一番難しいのがこのentryComponentsプロパティです。
このプロパティではオフラインコンパイルを行う時にエントリポイントとなるコンポーネントを指定します。
詳しく説明しようとするとまずオフラインコンパイルの説明からしないといけないので省略しますが、
ここに指定するコンポーネントは、
アプリケーションのエントリポイントになるコンポーネント
Lazy Loadingのよって実行時にあとから読み込まれるコンポーネント
以上のどちらかに当てはまるコンポーネントです。
ただし、bootstrapプロパティに指定してあるコンポーネントは自動的にentryComponentsにも追加されるので、
後者の、遅延ロードされるコンポーネントだけを指定することになります。

Lazy Loadingとは遅延読み込みの事見たいです。
ブログとかでテキスト読んでる間に、ふわっと画像が出てくるサイトとかあるじゃないですか、アレです。

で、そんなのつけてねーよ。と思い色々調べたんですが
書いてありました。

Ionic 3.0.0リリース – 2017年4月
Angular v4に対応する形でのアップデートです。Angularのメジャーバージョンアップにあわせて、Ionic本体もメジャーバージョンアップをしています(Ionic CLI内部的にはapp-scriptsでIonic 2系とIonic 3系のビルドを共通して処理する形)
AngularのLazy Loadingに対応する形で、Ionicも @IonicPage() によるLazy Loadingが実装されました。ionic g pageコマンド標準採用になったことから、Ionic 3ではすべてのpageをLazy Loadingでつくるのが一般的になりました。

うん、ionic gしたのでちゃんと遅延読み込みが実装されてるようですね。
どう見てもLazy Loadingです。
本当にありがとうございました。
ちなみに、Lazy Loading・・・めんどい
遅延読み込み対応のページはmodule.tsが同意ディレクトリに作成されるみたいです。

さて、解決方法ですが
せっかくなので
遅延読み込みを実装してみましょう。

Ionicでは、”Lazy Loading”するページでは、文字列として記述するため、
‘TaskListPage’
と、前後に ’ を付ける必要がありますが、app.module.ts で、importする必要はないらしいです。

ちなみにこちらで紹介されている本ですが、
先程引用させていただいたIonic 2 から 4 への、この2年間の進化を振り返る
こちらの情報を配信されている会社の代表様です。
是非ご参考になさって下さい。

私も買います。

では続けます。
ソースはこうなりますよね。

    this.pages = [
{ title: 'Hello Ionic', component: HelloIonicPage },
{ title: 'My First List', component: ListPage },
{ title: 'なんかかっこいいタイトル', component: 'TestMoodPage' }
];

変更点は"で囲う。以上

ここまでで新しくページを追加してリンク?をつけてみるまで完了です。
新しくページを追加したview
ハンバーガー消えちゃってるけどとりあえずはこれでいいや。

蛇足
色々調べていて、app.module.tsの@NgModule({

@NgModule({
declarations: [
MyApp,
HelloIonicPage,
ItemDetailsPage,
ListPage,
TestMoodPage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp),
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HelloIonicPage,
ItemDetailsPage,
ListPage,
TestMoodPage
],

declarations:
entryComponents:
この2つにTestMoodPageを追加
app.module.tsでTestMoodPageをimportしてやればページは表示させられましたが、遅延読み込みの方を使って記述していきます。
さっきの翻訳で書かれてたエラー
@ NgModule.entryComponentsに追加しましたか? noComponentFactoryError(http:// NgModule.entryComponents?
これって、@NgModuleのentryComponents:に追加しろってことですよね多分
ここらへんってangular調べればわかるんだっけ?

次は何しようかな。

ionic

Posted by ogin