Ionic + Angular で DRY な共有ナビゲーションコンポーネントを実装する方法
Ionic と Angular を使って共通ナビゲーションを実装する方法を解説。standalone コンポーネントを活用し、コードの重複を避けながら保守性の高いタブバーを実現する DRY な手法を紹介します。
目次
モバイルアプリ開発において、複数のページで同じナビゲーション要素を表示することはよくあります。しかし、各ページで同じコードを繰り返し記述するのは保守性の観点から好ましくありません。
今回は、Ionic と Angular の standalone コンポーネント機能を使って、DRY(Don’t Repeat Yourself)原則に基づいた共有ナビゲーションコンポーネントの実装方法をご紹介します。
ツールバーコンポーネントを1つにまとめる
当初、各ページのテンプレートに直接 ion-tab-bar を記述していました。
<!-- 各ページで同じコードを繰り返し -->
<ion-tab-bar slot="bottom">
  <ion-tab-button tab="dashboard" href="/forecasts/dashboard">
    <ion-icon aria-hidden="true" name="grid-outline"></ion-icon>
    <ion-label>ダッシュボード</ion-label>
  </ion-tab-button>
  <!-- 他のタブボタン... -->
</ion-tab-bar>
動作をチェックする面では問題なかったのですが、開発を続ける上で次のような問題が浮上してきました。
- コードの重複により同じコードを複数のページで記述する必要がある
 - 保守性が悪く、変更時に全ページを修正しなければならない
 - standalone コンポーネントで必要なインポートが不足し、型エラーが発生する
 hrefでは Angular ルーティングが動作しない
共有コンポーネントで問題を解決する
対策として、Angularの共通コンポーネントを作成する方法を試しました。
まず、Angular CLI を使って共有コンポーネントを生成します。今回は実験要素が強かったため、テストコードを省略しています。
npx ng generate component shared/bottom-tabs -- --skip-tests
続いて生成されたsrc/app/shared/bottom-tabs/bottom-tabs.component.tsに共通要素を実装していきます。TSファイル側では、利用したいアイコンや Ionic コンポーネントの登録などを行います。
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { 
  IonTabBar, 
  IonTabButton, 
  IonIcon, 
  IonLabel 
} from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { 
  gridOutline,
  locationOutline,
  colorPaletteOutline,
  sparklesOutline,
  informationCircleOutline
} from 'ionicons/icons';
@Component({
  selector: 'app-bottom-tabs',
  standalone: true,
  imports: [
    IonTabBar,
    IonTabButton,
    IonIcon,
    IonLabel,
    RouterLink  // ナビゲーションに必須
  ],
  templateUrl: './bottom-tabs.component.html',
  styleUrl: './bottom-tabs.component.scss'
})
export class BottomTabsComponent {
  constructor() {
    // 使用するアイコンを登録
    addIcons({
      'grid-outline': gridOutline,
      'location-outline': locationOutline,
      'color-palette-outline': colorPaletteOutline,
      'sparkles-outline': sparklesOutline,
      'information-circle-outline': informationCircleOutline
    });
  }
}
続いてsrc/app/shared/bottom-tabs/bottom-tabs.component.htmlにて、共通化したい要素のHTMLを実装します。
<ion-tab-bar slot="bottom">
  <ion-tab-button tab="dashboard" [routerLink]="['/forecasts/dashboard']">
    <ion-icon aria-hidden="true" name="grid-outline"></ion-icon>
    <ion-label>ダッシュボード</ion-label>
  </ion-tab-button>
  <ion-tab-button tab="area" [routerLink]="['/forecasts/area']">
    <ion-icon aria-hidden="true" name="location-outline"></ion-icon>
    <ion-label>地域予報</ion-label>
  </ion-tab-button>
  <ion-tab-button tab="rainbow" [routerLink]="['/forecasts/rainbow']">
    <ion-icon aria-hidden="true" name="color-palette-outline"></ion-icon>
    <ion-label>虹予報</ion-label>
  </ion-tab-button>
  <ion-tab-button tab="aurora" [routerLink]="['/forecasts/aurora']">
    <ion-icon aria-hidden="true" name="sparkles-outline"></ion-icon>
    <ion-label>オーロラ予報</ion-label>
  </ion-tab-button>
  
  <ion-tab-button tab="introduction" [routerLink]="['/forecasts/introduction']">
    <ion-icon aria-hidden="true" name="information-circle-outline"></ion-icon>
    <ion-label>アプリ紹介</ion-label>
  </ion-tab-button>
</ion-tab-bar>
共通化するコンポーネントができたので、各ページのコンポーネントでインポートしましょう。
// 例:weather-forecast.page.ts
import { BottomTabsComponent } from '../shared/bottom-tabs/bottom-tabs.component';
@Component({
  selector: 'app-weather-forecast',
  standalone: true,
  imports: [
    // 他のインポート...
    BottomTabsComponent
  ],
  // ...
})
export class WeatherForecastPage {
  // ...
}
その後、.htmlファイル側の実装も置き換えます。
<!-- weather-forecast.page.html -->
<ion-content>
  <!-- ページコンテンツ -->
</ion-content>
<app-bottom-tabs></app-bottom-tabs>
まとめ
Ionic + Angular の standalone コンポーネント機能を活用することで、効率的で保守性の高い共有ナビゲーションコンポーネントを実装できました。Angularはまだ AI にかなり支えてもらいながら使っている状態ですが、共通化などに慣れることで、アプリ開発への投入も進めていきたいなと思います。