2018.11.14
IonicのTabs(ion-tabs)を使ってスマホアプリを開発する上で、どのようなコンポーネントを作って、それらの遷移をどのように実装するのが理想かを検討してみた。今回作りたいUIは以下のようなイメージ。
$ ionic start
? What would you like to name your project: TabNavigationSample
? What starter would you like to use:
❯ tabs ............... ionic-angular A starting project with a simple tabbed interface
blank .............. ionic-angular A blank starter project
sidemenu ........... ionic-angular A starting project with a side menu with navigation in the content area
super .............. ionic-angular A starting project complete with pre-built pages, providers and best practices for Ionic development.
conference ......... ionic-angular A project that demonstrates a realworld application
tutorial ........... ionic-angular A tutorial based project that goes along with the Ionic documentation
aws ................ ionic-angular AWS Mobile Hub Starter
? Would you like to integrate your new app with Cordova to target native iOS and Android? (y/N) N
? Install the free Ionic Pro SDK and connect your app? (Y/n) n
この時点でのディレクトリ構成は以下のような感じ。
$ cd TabNavigationSample
$ tree -I node_modules .
.
├── ionic.config.json
├── package.json
├── src
│ ├── app
│ │ ├── app.component.ts
│ │ ├── app.html
│ │ ├── app.module.ts
│ │ ├── app.scss
│ │ └── main.ts
│ ├── assets
│ │ ├── icon
│ │ │ └── favicon.ico
│ │ └── imgs
│ │ └── logo.png
│ ├── index.html
│ ├── manifest.json
│ ├── pages
│ │ ├── about
│ │ │ ├── about.html
│ │ │ ├── about.scss
│ │ │ └── about.ts
│ │ ├── contact
│ │ │ ├── contact.html
│ │ │ ├── contact.scss
│ │ │ └── contact.ts
│ │ ├── home
│ │ │ ├── home.html
│ │ │ ├── home.scss
│ │ │ └── home.ts
│ │ └── tabs
│ │ ├── tabs.html
│ │ └── tabs.ts
│ ├── service-worker.js
│ └── theme
│ └── variables.scss
├── tsconfig.json
└── tslint.json
開発用のローカルサーバーを立ててブラウザで開いてみる。
$ ionic serve
「Home」「About」「Contact」の3つのタブを持つIonicアプリケーションが作成できた。
今回はIonicの基本的な使い方の手順というより、Tabsを使ったナビゲーションのベストプラクティスに関する考察なので、詳細な変更点や実装の説明は省略する。ソースコードはGitHubに公開予定。
以下のようなコンポーネントの構成を作ってみる。
エンティティ(ユーザーとか、記事とか)ベースでディレクトリを分けて、主要な機能ごとにコンポーネント名とした。このようなディレクトリ構成はWebアプリケーションフレームワークのRuby on Railsを参考にした。
NgModuleでのインポートが若干複雑になりそうなのでそこは後で詳しく触れる。
実装した後のディレクトリ構成は以下の通り。
$ tree -I "node_modules|www" .
.
├── ionic.config.json
├── package.json
├── src
│ ├── app
│ │ ├── app.component.ts
│ │ ├── app.html
│ │ ├── app.module.ts
│ │ ├── app.scss
│ │ ├── components.ts
│ │ └── main.ts
│ ├── assets
│ │ ├── icon
│ │ │ └── favicon.ico
│ │ └── imgs
│ │ └── logo.png
│ ├── index.html
│ ├── manifest.json
│ ├── models
│ │ ├── article.model.ts
│ │ └── user.model.ts
│ ├── navigations
│ │ └── tabs
│ │ ├── tabs.component.html
│ │ └── tabs.component.ts
│ ├── pages
│ │ ├── article
│ │ │ ├── index
│ │ │ │ ├── index.component.html
│ │ │ │ ├── index.component.scss
│ │ │ │ └── index.component.ts
│ │ │ └── show
│ │ │ ├── show.component.html
│ │ │ ├── show.component.scss
│ │ │ └── show.component.ts
│ │ └── user
│ │ ├── index
│ │ │ ├── index.component.html
│ │ │ ├── index.component.scss
│ │ │ └── index.component.ts
│ │ └── show
│ │ ├── show.component.html
│ │ ├── show.component.scss
│ │ └── show.component.ts
│ ├── service-worker.js
│ └── theme
│ └── variables.scss
├── tsconfig.json
└── tslint.json
エンティティごとにディレクトリを分けたことで、非常に見通しを良くすることができた。 ArticleとUserに関して、一覧画面と詳細画面がある構成が全く同じなので、今回はArticleモデルに着目して検討を進める。
一覧画面(src/pages/article/index/index.component.ts
)から詳細画面(src/pages/article/show/show.component.ts
)へのナビゲーションを確認する。
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { ShowPageComponent } from '../show/show.component';
import { Article } from '../../../models/article.model';
@Component({
selector: 'page-article-index',
templateUrl: './index.component.html'
})
export class IndexPageComponent {
articles: Article[] = [
new Article(1, 'Tabsを試してみる', 'Tabsの紹介記事です。'),
new Article(2, 'FabButtonを試してみる', 'FabButtonの紹介記事です。'),
new Article(3, 'InfiniteScrollを試してみる', 'InfiniteScrollの紹介記事です。'),
new Article(4, 'LoadingControllerを試してみる', 'LoadingControllerの紹介記事です。'),
];
constructor(
public navCtrl: NavController,
) { }
onArticleClicked(article: Article) {
this.navCtrl.push(ShowPageComponent, {
article: article,
});
}
}
onArticleClicked
メソッド内で、InjectされたNavController
のpush
メソッドを呼び出している。
一方、詳細画面は次のようになっている。
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Article } from '../../../models/article.model';
@Component({
selector: 'page-article-show',
templateUrl: './show.component.html'
})
export class ShowPageComponent {
article: Article;
constructor(
public navCtrl: NavController,
navParams: NavParams,
) {
this.article = navParams.get('article');
}
}
NavParams
をInjectして、get
メソッドでパラメタを受け取っている。
このようなディレクトリ構成を取ると、IndexPageComponent
のような名前のコンポーネントが大量にできてしまう。NgModule
に登録するときに多少工夫しないと、名前が重複してしまってうまくいかない。
src/app/app.module.ts
でimport
がたくさん並ぶと見づらいので、コンポーネントのリストをsrc/app/components.ts
で作ってそれをapp.module.ts
でimport
するようにしてみた。
// Navigations
import { TabsComponent } from '../navigations/tabs/tabs.component';
// Pages
import { IndexPageComponent as ArticleIndexPageComponent } from '../pages/article/index/index.component';
import { ShowPageComponent as ArticleShowPageComponent } from '../pages/article/show/show.component';
import { IndexPageComponent as UserIndexPageComponent } from '../pages/user/index/index.component';
import { ShowPageComponent as UserShowPageComponent } from '../pages/user/show/show.component';
export default [
TabsComponent,
ArticleIndexPageComponent,
ArticleShowPageComponent,
UserIndexPageComponent,
UserShowPageComponent,
];
import { NgModule, ErrorHandler } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { MyApp } from './app.component';
import components from './components';
@NgModule({
declarations: [
MyApp,
...components,
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
...components,
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
ArticleListComponent
、SingleArticleComponent
、ArticleReadUserComponent
(例:記事を読んだユーザーリスト)などどんどん無秩序に増えてしまって効率が悪い。