Ionic Tutorial

Lesson 3 Tabs, Modal 與輸入

Revised on 2025/9/15

Ionic v 8.0.0

Angular v 20.0.0

Outline

  • 使用者輸入與Modal元件
  • 再探Tabs版型

使用者輸入與Modal元件

  • 雙向繫結ngModel
  • 彈出式頁面:Modal
  • 表單驗證 FormBuilder

資料繫結(1/3)

tab1.page.ts

tab1.page.html

屬性繫結

資料流

事件流

事件繫結

事件產生

回呼函式

呼叫

定義屬性

使用屬性

資料繫結雙向繫結(2/3)

A.屬性繫結

tab1.page.html

tab1.page.ts

資料流

資料流

事件流

B.事件繫結

內插

單向繫結

事件繫結

C.雙向繫結

雙向繫結

資料流

雙向: 事件繫結 + 屬性繫結

事件流

➡ 資料即時更新

使用者輸入雙向繫結:搭配ngModal指令(3/3)

export class HomePage {
  item={
    name:'',
    due:'',
    notes:''
  };
<ion-list>
    <ion-item>
      <ion-label stacked>工作項目</ion-label>
      <ion-input type="text" [(ngModel)]="item.name"></ion-input>
    </ion-item>

    <ion-item>
      <ion-label stacked>到期日</ion-label>
      <ion-datetime displayFormat="MM/DD/YYYY" [(ngModel)]="item.due">
      </ion-datetime>
    </ion-item>

    <ion-item>
      <ion-label stacked>附註</ion-label>
      <ion-textarea [(ngModel)]="item.notes"></ion-textarea>
    </ion-item>

  </ion-list>

ts檔:定義屬性

html檔:使用[(ngModel)]

ionic輸入元件: ion-input, ion-datetime, ion-textarea

[(ngModel)] = "屬性名稱"

用法

// ...[略]...
export class HomePage {
  // 屬性: for interpolation
  userName = "小華";    // 姓名
  today = new Date();  // 日期
  // 屬性: for one-way binding
  isDisabled = true;
  imageUrl = 'https://ionicframework.com/docs/logos/ionic-text-docs-dark.svg'
  // 屬性: for event binding
  count = 0;    // 計數用

  constructor() {}

  // 事件處理器: 計數器加1
  increase() {
    this.count++;
  }
  // 事件處理器: 重置
  reset() {
    this.count=0;
  }
  // 事件處理器: 正反器flip-flop
  toggleButton() {
    this.isDisabled = !this.isDisabled; 
  }

}
ionic start BindingDemo blank
cd BindingDemo
ionic serve

建立範例

cmd

home.page.ts(部分)

<ion-header>
  <ion-toolbar>
    <ion-title>Ionic Data Binding Demo</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content class="ion-padding">
  <!-- Interpolation -->
  <h2>🔹 Interpolation</h2>
  <p>哈囉,我是 {{ userName }}!</p>
  <p>今天是:{{ today | date:'yyyy/MM/dd' }}</p>

  <ion-item>
    <ion-input label="輸入新名字" [(ngModel)]="userName"></ion-input>
  </ion-item>
  <hr />

  <!-- One-way Binding -->
  <h2>🔹 One-way Binding</h2>
  <ion-button color="success" [disabled]="isDisabled">不能按的按鈕</ion-button>
  <ion-button (click)="toggleButton()">切換按鈕狀態</ion-button>

  <p>顯示圖片:</p>
  <ion-img [src]="imageUrl"></ion-img>
  <hr />

  <!-- Event Binding -->
  <h2>🔹 Event Binding</h2>
  <p>目前計數:{{ count }}</p>
  <ion-button (click)="increase()">增加</ion-button>
  <ion-button color="danger" (click)="reset()">重設</ion-button>
</ion-content>

home.page.html

Modal元件彈出式頁面

開啟modal頁面

HomePage頁面

ModalPage頁面

關閉modal頁面

使用ModalController

呼叫create()

呼叫present()

使用ModalController

呼叫dismiss()

Modal元件範例說明

HomePage頁面

Modal頁面

Modal範例建立專案(1/9)

ionic start modalApp blank
cd modalApp
ionic g page modal
  1. 建立兩個頁面: HomePage, ModalPage
  2. HomePage呼叫presentModal(), 開啟ModalPage
  3. ModalPage編輯資料回傳給HomePage顯示

先了解modal頁面與主頁之間的基本運作!

Modal基本運作

HomePage(主頁) 呼叫presentModal() 開啟Modal頁面(附屬)

 Modal頁面(附屬)呼叫dismiss()關閉自己,返回HomePage(主頁)

連帶工作: 主頁傳遞參數、Modal頁面回傳結果、主頁處理回傳值

HomePage

ModalPage

基本運作HomePage開啟Modal(1/6)

import { ModalPage } from './../modal/modal.page';
import { Component } from '@angular/core';
import { ModalController } from '@ionic/angular';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  constructor(private modalCtrl: ModalController) { }

  async presentModal() {
    const modal = await this.modalCtrl.create({
      component: ModalPage
    });
    return await modal.present();
  }
}

❷ 引入ModalController

❶ constructor建立引數

❸ create()建立Modal頁面

基本運作: HomePage(主頁) 呼叫presentModal()開啟Modal頁面(附屬)

❹ present()顯示頁面

什麼是async 與 await?

home.page.ts

非同步, 依序執行

基本運作HomePage開啟Modal(2/6)

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="end">
      <ion-button (click)="presentModal()">
        <ion-icon slot="start" name="add"></ion-icon>
      </ion-button>
    </ion-buttons>
    <ion-title>
      工作表
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  ... 內容略 ...
</ion-content>

呼叫create(), present(), 顯示頁面

基本運作: HomePage(主頁) 開啟Modal頁面(附屬)

home.page.html

基本運作Modal頁面關閉(3/6)

基本運作: Modal頁面(附屬)關閉,返回HomePage(主頁) 

import { Component, OnInit } from '@angular/core';
import { ModalController } from '@ionic/angular';

@Component({
  selector: 'app-modal',
  templateUrl: './modal.page.html',
  styleUrls: ['./modal.page.scss'],
})
export class ModalPage implements OnInit {

  constructor(private modalCtrl: ModalController) { }

  ngOnInit() {
  }

  closeModal() {
    this.modalCtrl.dismiss();
  }
}

❷ 引入ModalController

❶ constructor建立引數

❸ dismiss() 關閉Modal頁面

modal.page.ts

基本運作Modal頁面關閉(4/6)

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="end">
      <ion-button (click)="closeModal()">
        <ion-icon slot="start" name="close"></ion-icon>
        關閉
      </ion-button>
    </ion-buttons>
    <ion-title>編輯</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  ... 內容略 ...
</ion-content>

呼叫dissmiss(), 關閉modal頁面

基本運作: Modal頁面(附屬)關閉,返回HomePage(主頁) 

modal.page.html

基本運作參數傳遞(5/6)

...略...
  async presentModal() {
    const modal = await this.modalCtrl.create({
      component: ModalPage,
      componentProps: {
        name: this.item.name,
        due: this.item.due,
        notes: this.item.notes
      }
    });
    modal.onDidDismiss().then((detail) => {
      if (detail.data !== undefined) {
        this.item = detail.data;
      }
    });
    return await modal.present();
  }

❶ 加入屬性componentProps, 傳值至modal視窗

home.page.ts

基本運作: 主頁傳遞參數, 處理回傳值

...略...
  async presentModal() {
    const modal = await this.modalCtrl.create({
      component: ModalPage
    });
    return await modal.present();
  }

原版本

❷  呼叫 onDidDismiss() 處理回傳結果

基本運作參數傳遞(6/6)

import { Component, OnInit } from '@angular/core';
import { ModalController, NavParams } from '@ionic/angular';
...略...
export class ModalPage implements OnInit {
  item = { name: '', due: '', notes: '' };

  constructor(private params: NavParams,
    private modalCtrl: ModalController) {
    this.item = {
      name: params.get('name'),
      due: params.get('due'),
      notes: params.get('notes')
    };
  }
...略...
  quitModal() {
    this.modalCtrl.dismiss();
  }
  closeModal() {
    this.modalCtrl.dismiss(this.item);
  }
}

modal.page.ts

基本運作: modal接收與回傳參數

❷ dismiss() 加入要回傳的參數

❶ 呼叫NavParams的get()接收參數

Modal範例修改app.module.ts(2/9)

...
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { ModalPageModule } from './modal/modal.module';

@NgModule({
  ...
  imports: [
    ModalPageModule, 
    BrowserModule, 
    IonicModule.forRoot(),
    AppRoutingModule
  ],
  ....
})
export class AppModule {}

import ModalPageModule

❶ 加入ModalPageModule, 重要!

加入imports陣列

app.module.ts

Modal範例修改app.module.ts(2/9)

...
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { ModalPageModule } from './modal/modal.module';

@NgModule({
  ...
  imports: [
    ModalPageModule, 
    BrowserModule, 
    IonicModule.forRoot(),
    AppRoutingModule
  ],
  ....
})
export class AppModule {}

❶ 加入ModalPageModule, 重要!

app.module.ts

若未加入, 則出現執行錯誤!

Modal範例修改app-routing.module.ts(3/9)

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

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', loadChildren: './home/home.module#HomePageModule' },
  // { path: 'modal', loadChildren: './modal/modal.module#ModalPageModule' },
];

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

 刪去'modal'路徑

modal路徑用不到

app-routing.module.ts

Modal範例HomePage開啟Modal頁面(4/9)

home.page.ts(部分)

...
import { ModalController } from '@ionic/angular';
import { ModalPage } from './../modal/modal.page';
import { DatePipe } from '@angular/common';
...

export class HomePage {
  item = {
    name: '工作事項',
    due: new DatePipe('en-US').transform(new Date(), 'MM-dd-yyyy'),
    notes: '備註'
  };  // 工作事項
  constructor(private modalCtrl: ModalController) {  }
  ...
}

❷ 引入ModalController

❶ constructor建立引數

建立日期(當天)

設定格式:月-日-年

轉成字串

設計HomePage畫面與資料屬性

工作表有三個欄位:名稱、建立日期、備註

today = new Date(); 
{{ today }}

 Sat Jun 15 2019 22:21:08 GMT+0530

t1 = new DatePipe('en-US');   // 日期格式編排, 西元年格式
d = new Date();
t2 = new  DatePipe('en-US').transform(d, 'MM-dd-yyyy');

06-15-2019

{{ t2 }}
...略...
  async presentModal() {
    const modal = await this.modalCtrl.create({
      component: ModalPage,
      componentProps: {
        name: this.item.name,
        due: this.item.due,
        notes: this.item.notes
      }
    });
    // 註冊接收回傳結果的函式
    modal.onDidDismiss().then((detail) => {
      if (detail.data !== undefined) {  // 有資料回傳回來
        this.item = detail.data;
        this.item.due = new DatePipe('en-US')
            .transform(this.item.due, 'MM-dd-yyyy');
      }
    });
    return await modal.present();

❶ 加入屬性componentProps, 傳值至modal視窗

❷  呼叫 onDidDismiss() 處理回傳結果

Modal範例HomePage開啟Modal頁面(5/9)

home.page.ts(部分)

Modal範例HomePage開啟Modal頁面(6/9)

import { ModalPage } from './../modal/modal.page';
import { Component } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  item = {
    name: '工作事項',
    due: new DatePipe('en-US').transform(new Date(), 'MM-dd-yyyy'),
    notes: '備註'
  };  // 工作事項列表
  constructor(private modalCtrl: ModalController) {  }

  async presentModal() {
    const modal = await this.modalCtrl.create({
      component: ModalPage,
      componentProps: {
        name: this.item.name,
        due: this.item.due,
        notes: this.item.notes
      }
    });
    modal.onDidDismiss().then((detail) => {
      if (detail.data !== undefined) {
        this.item = detail.data;
        this.item.due = new DatePipe('en-US').transform(this.item.due, 'MM-dd-yyyy');
      }
    });
    return await modal.present();
  }
}

home.page.ts(完整)

Modal範例HomePage開啟Modal頁面(7/9)

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="end">
        <ion-button (click)="presentModal()">
            <ion-icon slot="start" name="create"></ion-icon>
          </ion-button>
    </ion-buttons>
    <ion-title>
      工作表
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
    <ion-list>
        <ion-item>{{ item.name }}</ion-item>
        <ion-item>{{ item.due }}</ion-item>
        <ion-item>{{ item.notes }}</ion-item>
      </ion-list>
</ion-content>

home.page.html

Modal範例Modal頁面: 回傳與關閉(8/9)

import { Component, OnInit } from '@angular/core';
import { ModalController, NavParams } from '@ionic/angular';
...
export class ModalPage implements OnInit {
  item = { name: '', due: '', notes: '' };

  constructor(private params: NavParams,
    private modalCtrl: ModalController) {
    this.item = {
      name: params.get('name'),
      due: params.get('due'),
      notes: params.get('notes')
    };
  }
  ngOnInit() {}
  
  quitModal() {
    this.modalCtrl.dismiss();
  }
  closeModal() {
    this.modalCtrl.dismiss(this.item);
  }
}

 引入ModalController, NavParams

建立ModalController, NavParams引數

❸ 讀取傳送過來的資料

❹ 回傳編修資料(dismiss()則不回傳)

modal.page.ts(部分)

Modal範例Modal頁面: 回傳與關閉(9/9)

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="end">
      <ion-button (click)="quitModal()">
        <ion-icon slot="start" name="close"></ion-icon>
        關閉
      </ion-button>
    </ion-buttons>
    <ion-title>編輯</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list>
    <ion-item>
      <ion-label position="stacked">工作項目</ion-label>
      <ion-input type="text" [(ngModel)]="item.name"></ion-input>
    </ion-item>
    <ion-item>
      <ion-label position="stacked">到期日</ion-label>
      <ion-datetime display-format="MM/DD/YYYY" [(ngModel)]="item.due"></ion-datetime>
    </ion-item>
    <ion-item>
      <ion-label position="stacked">附註</ion-label>
      <ion-textarea [(ngModel)]="item.notes"></ion-textarea>
    </ion-item>
  </ion-list>
  <ion-button (click)="closeModal()">Save</ion-button>
  <ion-button (click)="quitModal()">Cancel</ion-button>
</ion-content>

modal.page.html

Modal範例Modal頁面: 回傳與關閉(9/9)

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="end">
      <ion-button (click)="quitModal()">
        <ion-icon slot="start" name="close"></ion-icon>
        關閉
      </ion-button>
    </ion-buttons>
    <ion-title>編輯</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list>
    <ion-item>
      <ion-label position="stacked">工作項目</ion-label>
      <ion-input type="text" [(ngModel)]="item.name"></ion-input>
    </ion-item>
    <ion-item>
      <ion-label position="stacked">到期日</ion-label>
      <ion-datetime display-format="MM/DD/YYYY" [(ngModel)]="item.due"></ion-datetime>
    </ion-item>
    <ion-item>
      <ion-label position="stacked">附註</ion-label>
      <ion-textarea [(ngModel)]="item.notes"></ion-textarea>
    </ion-item>
  </ion-list>
  <ion-button (click)="closeModal()">Save</ion-button>
  <ion-button (click)="quitModal()">Cancel</ion-button>
</ion-content>

modal.page.html

<ion-item>
    <ion-label>Input</ion-label>
    <ion-input></ion-input>
</ion-item>

i-item-input

Ionic輸入元件

  • 基本格式
  • 輸入元件範例

Ionic輸入元件基本格式(1/2)

<ion-content>
  <ion-list>
    <ion-item>
      <ion-label position="stacked">工作項目</ion-label>
      <ion-input type="text" [(ngModel)]="item.name"></ion-input>
    </ion-item>
    <ion-item>
      <ion-label position="stacked">到期日</ion-label>
      <ion-datetime display-format="MM/DD/YYYY" [(ngModel)]="item.due"></ion-datetime>
    </ion-item>
    <ion-item>
      <ion-label position="stacked">附註</ion-label>
      <ion-textarea [(ngModel)]="item.notes"></ion-textarea>
    </ion-item>
  </ion-list>
  <ion-button (click)="closeModal()">Save</ion-button>
  <ion-button (click)="quitModal()">Cancel</ion-button>
</ion-content>

 每一個欄位使用一組ion-item

❶ 通常以ion-list 「清單列表」方式呈現

❸ 「清單列表」外,加上「送出」或「取消」按扭

 ion-label                     :標題

 ion-input 或其他指令: 輸入欄位

[(ngModel)]                 :綁定屬性

click事件繫結,呼叫函式處理表單

Ionic輸入元件輸入元件範例(2/2)

<ion-item>
    <ion-label position="stacked">工作項目</ion-label>
    <ion-input type="text" [(ngModel)]="item.name"></ion-input>
</ion-item>

 日期時間欄位: ion-datetime

❶ 單行文字欄位

❸ 多行文字欄位:ion-textarea

以display-format設定格式

ionic輸入元件: ion-input, ion-datetime, ion-textarea

<ion-item>
    <ion-label position="stacked">到期日</ion-label>
    <ion-datetime display-format="MM/DD/YYYY" [(ngModel)]="item.due"></ion-datetime>
</ion-item>
<ion-item>
    <ion-label position="stacked">附註</ion-label>
    <ion-textarea [(ngModel)]="item.notes"></ion-textarea>
</ion-item>

練習使用...

  • 開關 ion-toggle
  • 多選 ion-checkbox
  • 單選 ion-radio
  • 下拉選單 ion-select
  • 捲軸 ion-range
  • 搜尋框 ion-searchbar
  • ...更多在ionic元件頁面

再探Tabs版型

  • ionic start tabsApp tabs

Tabs版型版型架構(1/5)

ionic start tabsApp tabs
ionic serve

建立tabs版型專案

3 pages
+

tabs版型

自訂元件

Tabs版型版型架構(2/5)

import { Component } from '@angular/core';

@Component({
  selector: 'app-tabs',
  templateUrl: 'tabs.page.html',
  styleUrls: ['tabs.page.scss']
})
export class TabsPage {}

tabs.page.ts

<ion-tabs>
  <ion-tab-bar slot="bottom">
    <ion-tab-button tab="tab1">
      <ion-icon name="flash"></ion-icon>
      <ion-label>Tab One</ion-label>
    </ion-tab-button>

    <ion-tab-button tab="tab2">
      <ion-icon name="apps"></ion-icon>
      <ion-label>Tab Two</ion-label>
    </ion-tab-button>

    <ion-tab-button tab="tab3">
      <ion-icon name="send"></ion-icon>
      <ion-label>Tab Three</ion-label>
    </ion-tab-button>
  </ion-tab-bar>
</ion-tabs>

tabs.page.html

不會更動

需更動

Tabs版型版型架構(3/5)

<ion-tabs>
  <ion-tab-bar slot="bottom">
    <ion-tab-button tab="tab1">
      <ion-icon name="flash"></ion-icon>
      <ion-label>Tab One</ion-label>
    </ion-tab-button>

    <ion-tab-button tab="tab2">
      <ion-icon name="apps"></ion-icon>
      <ion-label>Tab Two</ion-label>
    </ion-tab-button>

    <ion-tab-button tab="tab3">
      <ion-icon name="send"></ion-icon>
      <ion-label>Tab Three</ion-label>
    </ion-tab-button>
  </ion-tab-bar>
</ion-tabs>

tabs.page.html

一個頁籤

圖示

文字

ion-tab-bar設定位置

出現在頁尾

Tabs版型版型架構(4/5)

關於圖示

<ion-icon name="apps"></ion-icon>

Tabs版型版型架構(5/5)

Tabs版型範例(1/17)

首頁

navigate()

[routerLink]

熱門

以點擊次數排序

ion-badge

ionic g page detail

每點擊一次

ion-badge顯示值+1

Tabs版型範例(2/17)

// 學院資料
export const COLLEGES = [
  { cid: 1, hits: 500, title:'人文學院', intro:'馬偕博士在此建立牛津學堂,使此地成為早期臺灣接受西方文化的重要據點。為承先啟後,延續這種人文傳統,本校人文學院將秉持立足臺灣、胸懷世界的理念,重視本土的文學研究,並參與國際學術社會的研究與討論。', picture:'humanity.png'},
  { cid: 2, hits: 400, title:'觀光休閒與運動學院', intro:'民國54年奉 教育部核准設立「私立淡水工商管理專科學校」。民國83年奉准改制為四年制獨立學院,定名為:「私立淡水工商管理學院」,民國88年8月1日,奉准改名為「真理大學」,英文校名為Aletheia University。', picture:'tourism.jpg'},
  { cid: 3, hits: 300, title:'管理學院', intro:'本校前身牛津學堂,為加拿大長老教會馬偕博士於1882年淡水現址創立。1959年向教育部申請設立大學,1965年獲准,校名淡水工商管理專科學校(Tamsui Institute of Business Administration)。', picture:'management.gif'},
  { cid: 4, hits: 200, title:'財經學院', intro:'目前本學院設有財務金融學系、財政稅務學系、經濟學系(含財經碩士班)、國際貿易學系、會計資訊學系、法律學系(含碩士班)共六系。 ', picture:'finance.jpg'},
  { cid: 5, hits: 100, title:'資訊與商業智慧學院', intro:'本學院設有資訊工程學系、財務與精算學系等二個學系,為一小而美的精緻型學院。各學系課程的設計,基本上以數學方面的常用知識為核心,再將觸角延伸至與數學較為相關的其他領域。', picture:'information.jpg'}
];

延伸問題: 資料重複了!
資料與HomePage不一致(沒有同步修改)怎麼辦?

需要資料服務元件
資料集中管理

陣列資料: 五筆資料,每一筆5個欄位

需重複放在2個頁面裡

Services!!!

資料存取物件(DAO)設計模式

目標:建立services,以便集中管理資料

Data Access Object (DAO) design pattern

Icons made by monkik from  www.flaticon.com is licensed by CC 3.0 BY

Icons made by Freepik from  www.flaticon.com is licensed by CC 3.0 BY

軟體解決方案

問題(經常發生)

得到解決

設計模式

資料存取物件設計模式(1/3)

資料存取物件設計模式(2/3)

資料模型TS

資料介面TS

資料服務

資料庫

實作

TS檔

使用

schema

對應

存取

元件

TS檔

HTML檔

屬性繫結

事件繫結

注入

呼叫服務

資料存取設計模式

資料存取物件設計模式DAO實作(3/3)

資料模型TS

資料介面TS

資料服務

資料庫

實作

TS檔

使用

schema

對應

存取

元件

TS檔

HTML檔

屬性繫結

事件繫結

注入

呼叫服務

Ⓐ 新增models資料夾
新增「資料模型.ts檔」

Ⓑ 在models資料夾中
新增「資料界面.ts檔」

Ⓒ 建立服務

ionic g service 服務名稱

實作資料存取

Ⓓ 使用服務

Tabs版型範例建立專案(3/17)

ionic start tabsApp tabs
cd tabsApp
ionic g service services/data
ionic g page detail 
# 刪除tab2或tab3資料夾,修改tabs.page.html/tabs.router.module.ts
  1. 建立tabs版型專案
  2. 使用services做為資料來源
  3. 手動建立models資料夾
    • 新增[資料模型.ts], [DAO介面.ts]
       
  4. 頁面瀏覽與傳遞參數:navigate()
  5. 參數讀取: snapshot.paramMap.get()

資料服務

資料模型.ts

DAO介面.ts

由Router提供

由ActivatedRoute提供

  • 將"services範例圖片.zip"解壓縮放在/assets下 

檔案在ilms裡

圖片資源

Tabs版型範例建立專案(4/17)

<ion-tabs>

  <ion-tab-bar slot="bottom">
    <ion-tab-button tab="tab1">
      <ion-icon name="home"></ion-icon>
      <ion-label>學術單位</ion-label>
    </ion-tab-button>

    <ion-tab-button tab="tab2">
      <ion-icon name="flame"></ion-icon>
      <ion-label>熱門</ion-label>
    </ion-tab-button>
  </ion-tab-bar>

</ion-tabs>

修改 tabs.page.ts

  1. 留下2個tabs
  2. 分別修改ion-label與ion-icon

Tabs版型範例建立專案(5/17)

import { NgModule } from '@angular/core';
....
const routes: Routes = [
  {
    path: 'tabs',
    component: TabsPage,
    children: [
      ....
      {
        path: 'tab3',
        children: [
          {
            path: '',
            loadChildren: '../tab3/tab3.module#Tab3PageModule'
          }
        ]
      },
      ....
  },
  ...
];
...

修改 tabs-routing.module.ts

找到並刪除 path: 'tab3' 區塊

Tabs版型範例實作DAO(6/17)

export interface College {
    cid: number;        // 學院代號
    hits: number;       // 點擊數
    title: string;      // 院名
    intro: string;      // 學院介紹
    picture: string;    // 圖片檔名
}

❸-Ⓐ 定義資料模型 models/college.ts

學院資料: 5個欄位

❸-Ⓑ 定義DAO介面 models/collegeDAO.ts

import { College } from './college';

export interface CollegeDAO {
    getAll(): College[];             // 回傳所有資料
    getCollege(id: number): College; // 回傳指定學院資料
    getHotest(): College[];          // 回傳依據點擊數排列的資料
    hit(id: number): void;           // 更新點擊數
}

實作DAO需提供4個函式

Tabs版型範例實作DAO:建立服務(7/17)

import { Injectable } from '@angular/core';
import { CollegeDAO } from './../models/collegeDAO';
import { College } from '../models/college';

// 學院資料常數
export const COLLEGES: College[] = [
  { cid: 1, hits: 500, title: '人文學院', intro: '馬偕博士在此建立牛津學堂,使此地成為早期臺灣接受西方文化的重要據點。為承先啟後,延續這種人文傳統,本校人文學院將秉持立足臺灣、胸懷世界的理念,重視本土的文學研究,並參與國際學術社會的研究與討論。', picture:'humanity.png'},
  { cid: 2, hits: 400, title: '觀光休閒與運動學院', intro: '民國54年奉 教育部核准設立「私立淡水工商管理專科學校」。民國83年奉准改制為四年制獨立學院,定名為:「私立淡水工商管理學院」,民國88年8月1日,奉准改名為「真理大學」,英文校名為Aletheia University。', picture:'tourism.jpg'},
  { cid: 3, hits: 300, title: '管理學院', intro: '本校前身牛津學堂,為加拿大長老教會馬偕博士於1882年淡水現址創立。1959年向教育部申請設立大學,1965年獲准,校名淡水工商管理專科學校(Tamsui Institute of Business Administration)。', picture:'management.gif'},
  { cid: 4, hits: 200, title: '財經學院', intro: '目前本學院設有財務金融學系、財政稅務學系、經濟學系(含財經碩士班)、國際貿易學系、會計資訊學系、法律學系(含碩士班)共六系。 ', picture:'finance.jpg'},
  { cid: 5, hits: 100, title: '資訊與商業智慧學院', intro: '本學院設有資訊工程學系、財務與精算學系等二個學系,為一小而美的精緻型學院。各學系課程的設計,基本上以數學方面的常用知識為核心,再將觸角延伸至與數學較為相關的其他領域。', picture:'information.jpg'}
];

@Injectable({
  providedIn: 'root'
})
...
  1. 加入常數COLLEGES
  2. 注意此類別為@Injectable()

     
  3. implements CollegeDAO
  4. 提供4個服務methods

data.service.ts

❸-Ⓒ 根據介面,建立服務 services/data.service.ts

...
export class DataService implements CollegeDAO {
  ...
  // 取得所有學院資料
  getAll(): College[] {}
  // 取得單一學院資料
  getCollege(id: number): College {}
  // 取得根據'點擊數'排序所有學院的資料
  getHotest(): College[] {}

  hit(id: number) {}
}

Tabs版型範例實作DAO:getAll() (8/17)

...
export const COLLEGES: College[] = [
  { cid: 1, hits: 500, title:'人文學院', intro:'馬偕博士在此建立牛津學堂,使此地成為早期臺灣接受西方文化的重要據點。為承先啟後,延續這種人文傳統,本校人文學院將秉持立足臺灣、胸懷世界的理念,重視本土的文學研究,並參與國際學術社會的研究與討論。', picture:'humanity.png'},
  { cid: 2, hits: 400, title:'觀光休閒與運動學院', intro:'民國54年奉 教育部核准設立「私立淡水工商管理專科學校」。民國83年奉准改制為四年制獨立學院,定名為:「私立淡水工商管理學院」,民國88年8月1日,奉准改名為「真理大學」,英文校名為Aletheia University。', picture:'tourism.jpg'},
 ...
];
...
export class DataService implements CollegeDAO {
  colleges = COLLEGES;  // 定義新屬性,值設定為學院資料常數

  ...
  // 取得所有學院資料
  getAll(): College[] {
    return this.colleges;
  }
  ...
}

❸-Ⓒ 1. 建立屬性、完成查詢功能getAll()

data.service.ts

Tabs版型範例實作DAO: getCollege(id) (9/17)

this.colleges: 陣列

    filter(): 陣列元素過濾功能, 結果為一陣列

    過濾條件:x.cid === id

...
export class DataService {
  colleges = COLLEGES;  // 屬性

  ...
  // 取得單一學院資料
  getCollege(id: number): College {
    return this.colleges.filter(x => x.cid === id)[0];
  }
  ...
}

❸-Ⓒ 2. 完成查詢功能getCollege()

Tabs版型範例實作DAO: getHotest() (10/17)

回呼函式:決定排序規則
回傳1為a比b大的條件
回傳-1為a比b小的條件
回傳0為a,b一樣大

...
export class DataService {
  ...
  // 取得根據'點擊數'排序所有學院的資料
  getHotest(): College[] {
    const lists = this.colleges.slice(); // slice():複製陣列
    const sorted = lists.sort(
      (a, b) => 
      { // (a,b): 比較函式,陣列兩個元素a,b何者為大
        if (a.hits > b.hits) {
          return 1;   // a 比 b 大
        }
        if (a.hits < b.hits) {
          return -1;  // a 比 b 小
        }
        return 0;   // 一樣大
      }
    );
    return sorted.reverse(); // sort()預設排序由小到大
  }
  ...
}

(a, b) => { ...}

函式格式

參數

函式主體

❸-Ⓒ 3. 完成查詢功能getHotest()

Tabs版型範例實作DAO: hit(id) (11/17)

...
export class DataService {
  colleges = COLLEGES;  // 屬性

  // 取得單一學院資料
  getCollege(id: number): College {
    return this.colleges.filter(x => x.cid === id)[0];
  }
  ...
  // 增加點擊次數
  hit(id: number) {
    this.getCollege(id).hits++;
  }
  ...
}

❸-Ⓒ 4. 完成修改點擊資料功能hit()

可運用已經寫好的getCollege()
取得單一學院資料

Tabs版型範例使用服務:Tab1Page (12/17)

import { Router } from '@angular/router';
import { DataService } from './../services/data.service';
import { Component } from '@angular/core';
import { College } from '../models/college';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  lists: College[];

  constructor(
    private ds: DataService,
    private router: Router
    ) {
    this.lists = ds.getAll();
  }

  details(id) {
    this.router.navigate(['/detail', {cid: id}]);
  }
}

tab1.page.ts

Ⓐ import 服務

在建構子內建立引數

使用 getAll() 服務

navigate() 切換頁面, 傳遞參數cid

❹ 使用服務建立「學術單位」列表頁面(ts檔)

Tabs版型範例使用服務:Tab1Page (13/17)

<ion-header>
  <ion-toolbar>
    <ion-title>
      學術單位
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
    <ion-list>
      <ion-item *ngFor="let item of lists" (click)="details(item.cid);">
        <ion-label>{{item.title}}</ion-label>
      </ion-item>
    </ion-list>
</ion-content>

tab1.page.html

❹ 使用服務建立「學術單位」列表頁面(html檔)

Tabs版型範例使用服務:Tab2Page (14/17)

import { ActivatedRoute } from '@angular/router';
import { DataService } from './../services/data.service';
import { Component } from '@angular/core';
import { College } from '../models/college';

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  lists: College[];
  constructor(private ds: DataService) {
    this.lists = ds.getHotest();
  }
}

tab2.page.ts

Ⓐ import 服務

在建構子內建立引數

使用 getHotest() 服務

使用服務完成「熱門單位」列表頁面(ts檔)

Tabs版型範例使用服務:Tab2Page (15/17)

<ion-header>
  <ion-toolbar>
    <ion-title>
      熱門
    </ion-title>
  </ion-toolbar>
</ion-header>
<ion-content>
    <ion-list>
        <ion-item *ngFor = "let item of lists">
          {{item.title}}
          <ion-badge slot="end">{{item.hits}}</ion-badge>
        </ion-item>
      </ion-list>
</ion-content>

tab2.page.html

ion-badge: 以badge方式顯示「點擊次數」

什麼是badge?

使用服務完成「熱門單位」列表頁面(html檔)

Tabs版型範例使用服務:DetailPage (16/17)

import { ActivatedRoute } from '@angular/router';
import { College } from './../models/college';
import { Component, OnInit } from '@angular/core';
import { DataService } from '../services/data.service';

@Component({
  selector: 'app-detail',
  templateUrl: './detail.page.html',
  styleUrls: ['./detail.page.scss'],
})
export class DetailPage implements OnInit {
  college: College;

  constructor(
    private ds: DataService,
    private route: ActivatedRoute
  ) { }

  ngOnInit() {
    const cid = this.route.snapshot.paramMap.get('cid'); // 從參數讀取學院id
    this.college = this.ds.getCollege(Number(cid)); // 經由學院id取得學院資料
    this.ds.hit(Number(cid));   // 呼叫服務讓點擊次數加1
  }
}

detail.page.ts

讀取參數cid

取得學院資料

增加點擊次數

注意Number(cid): 將字串轉成數值

使用服務在進入「細節」頁面時,記錄點選次數(ts檔)

Tabs版型範例使用服務:DetailPage (17/17)

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button></ion-back-button>
    </ion-buttons>
    <ion-title>{{ college.title }}</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-card>
    <img src="/assets/imgs/{{ college.picture }}" />
    <ion-card-content>
      <ion-card-title>
        {{ college.title }}
      </ion-card-title>
      <p>{{ college.intro }}</p>
    </ion-card-content>
  </ion-card>
</ion-content>

detail.page.html

使用服務在進入「細節」頁面時,記錄點選次數(html檔)