2018年3月22日 星期四

【Ionic 3】頁面瀏覽:參數傳遞

延續Ionic 3頁面瀏覽之push/pop機制,在頁面切換時,另一個重要問題是頁面之間的「參數傳遞」。以常見的「清單列表」/「細節」頁面切換為例,除了呼叫push()外,還必須傳送如「項目編號」的參數到下一個頁面,如下圖所示:
圖1 點選清單項目,必須傳遞參數到「細節」頁面,如"id:2330"。
參數傳遞的工作,在傳送端—亦即上圖的「清單列表」頁面—可將參數以物件的形式,直接加在push()的第二個引數:
    this.navCtrl.push(DetailPage, {
      id: 2330,
      arg: some_value
    });

而接收端—即「細節」頁面—則必須透過NavParams元件的協助,以NavParams提供的get()讀取資料:
import { NavController, NavParams } from 'ionic-angular';
...
export class DetailPage {
  id=0;

  constructor(public navCtrl: NavController, public navParams: NavParams) {
    this.id = this.navParams.get('id');
  }
...
範例專案
首先建立新專案,並建立新頁面detail。原本的home頁面將用來顯示清單,而點選清單項目後,則會跳至detail頁面,顯示細節:
ionic start listApp blank
cd listApp
ionic g page detail --no-module
另外,由於清單與細節會共用同一組陣列資料,資料部份使用provider,讓兩個頁面可以共用,因此還必須建立提供資料的provider:
ionic g provider data
建完之後,專案架構如下圖:
圖2 專案架構圖:兩個頁面detail與home,另有data provider。
ionic g provider指令產生Angular框架裡的Service元件,Ionic 3則稱之為provider,事實上ionic provider必須符合Angular Service元件的寫法與使用方式。
1.1 修正app.module.ts:加入HttpClientModule
ionic CLI產生provider時,會建立一個ts檔(此例為providers/data/data.ts),並自動修改app.module.ts。data.ts程式碼如下:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable()
export class DataProvider {
  constructor(public http: HttpClient) {
    console.log('Hello DataProvider Provider');
  } 
}
  1. 第2~4行:Injectable為Angular Service元件必須使用之decorator,因此第2行import,第4行加上@Injectable decorator,讓此元件成為可被注入(inject)之服務。
  2. 第5行:provider元件之類別名稱為DataProvider。若檢視app.module.ts,便會看到CLI已自動將此元件加入app.module.ts:
...
import { DataProvider } from '../providers/data/data';

@NgModule({
  ...
  providers: [
    ...,
    DataProvider
  ]
上述data.ts以及自動修改的app.module.ts看似已符合Angular Service元件寫法,後續可將DataProvider注入其他元件使用。然而執行時,卻會出現錯誤訊息,此因目前最新之ionic CLI版本(3.20.0)並未正確補足app.module.ts裡面的定義。
檢視data.ts的第1與第6行都用到了HttpClient元件,然而app.module.ts並不知道要去何處載入HttpClient元件來使用,因此,必須先import HttpClientModule—也就是HttpClient所屬之模組到AppModule裡,故修正app.module.ts如下,將HttpClientModule import進來,並加入模組的imports屬性裡:
import { HttpClientModule } from '@angular/common/http';
... @NgModule({ ... imports: [ BrowserModule, HttpClientModule, IonicModule.forRoot(MyApp) ],

1.2 撰寫DataProvider
由於DataProvider只須提供兩個頁面共用資料,因此,只須在data.ts裡定義一個常數陣列,再加上讀取陣列全部元素(用於清單顯示)、以及讀取特定元素(用於顯示特定項目細節)兩個methods即可。完整之data.ts如下:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

export const DATA=[
  {id: 2330, name: '台積電', price: 147.0, PER: 14.44, PBR: 3.65,
      yield: 3.06, Desc: '晶圓製造'},
  {id: 1301, name: '台塑', price: 76.5, PER: 27.03, PBR: 1.7,
      yield: 2.22, Desc: '聚氯乙烯(PVC)、氯乙烯等塑膠製品'},
  {id: 2002, name: '中鋼', price: 25.8, PER: 18.04, PBR: 1.33,
      yield: 3.88, Desc: '鋼品設計製造買賣儲運及其他相關業務'},
  {id: 3045, name: '台灣大', price: 108.0, PER: 19.42, PBR: 6.13,
      yield: 5.19, Desc: '通訊業'},
  {id: 2454, name: '聯發科', price: 421.5, PER: 14.03, PBR: 2.68,
      yield: 3.56, Desc: '多媒體IC 、電腦週邊IC、其他IC'},
  {id: 2317, name: '鴻海', price: 93.1, PER: 10.52, PBR: 1.48,
      yield: 1.93, Desc: '電腦系統設備連接器等之開發設計製造'},
  {id: 3008, name: '大立光', price: 2695.0, PER: 18.6, PBR: 7.83,
      yield: 1.06, Desc: '各式光學鏡頭模組研發設計生產銷售'}
];
@Injectable()
export class DataProvider {
  lists = DATA;
  constructor(public http: HttpClient) {
    console.log('Hello DataProvider Provider');
  }
  /* 回傳所有項目 */
  getLists(){
    return this.lists;
  }
  /* 回傳單一項目 */
  getItem(id){
    return this.lists.filter(x => x.id == id)[0];
  }

}
  1. 第4-19行:定義共用的常數資料陣列DATA。每個元素有id, name, price, PER, PBR, yield, Desc等屬性。
  2. 第22行:定義DataProvider的屬性lists,讓其等於常數陣列DATA,以便提供存取DATA陣列內容的服務。
  3. 第27-29行:取得陣列所有元素。
  4. 第31-33行:取得id所屬的某個陣列元素。this.lists.filter()為typescript的陣列過濾函式,x為iterator,依序處理陣列每一個元素,因此符合x.id與傳進來的參數id相等的陣列元素,便會過濾出來。而因DATA陣列id值都是唯一,故回傳過濾結果的第一個元素(即[0])。
2.1 撰寫清單頁面:HomePage元件
清單頁面從DataProvider取得陣列所有元素,加以顯示。另外,每個清單項目必須加上click事件處理:當點選時,push「細節元件」進瀏覽堆疊,同時也有附上id值作為參數。home.ts程式碼如下:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

import { DetailPage } from '../detail/detail';
import { DataProvider } from '../../providers/data/data';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html',
 
})
export class HomePage {
  lists=[];
  constructor(public navCtrl: NavController, public dp: DataProvider) {
    this.lists = this.dp.getLists();
  }
  showDetail(id){
    this.navCtrl.push( DetailPage, {
      id: id
    } );
  }
}
  1. 第4-5行:import兩個所需元件。DataProvider即提供資料服務的元件。
  2. 第14行:在constructor中設定DataProvider引數dp,以便透過dp取得資料服務。
  3. 第15行:透過dp呼叫getLists()服務(在data.ts內),取得DATA陣列內容,設定給屬性lists。
  4. 第17-21行:滑鼠點選清單各個項目時的處理函式。函式中呼叫this.navCtrl.push()設定瀏覽堆疊(navigation stack)最上層頁面-DetailPage,push()第二個引數便是傳送給DetailPage的參數。此處傳送的物件有一個id屬性,其數值則為showDetail(id)傳送進來的數值,該數值會在home.html內設定。
home.html頁面如下,除了使用ion-list排列清單外,在每個清單項目ion-item處,則必須設定滑鼠點選事件(click),讓其呼叫showDetail(),並將參數設定為自己這個項目的id值(如第11行的item.id):
<ion-header>
  <ion-navbar>
    <ion-title>
      Demo: 傳遞資料
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-list>
    <ion-item *ngFor="let item of lists" (click)="showDetail(item.id)">
      {{ item.id }} {{ item.name }}
    </ion-item>
  </ion-list>
</ion-content>

2.2 撰寫細節頁面:DetailPage
因DetailPage建立時,帶有--no-module參數,因此,必須先將DetailPage元件加入AppModule中,亦即修改app.module.ts,import DetailPage,並將DetailPage元件加入模組的declarations與entryComponents屬性中:
...
import { DetailPage } from '../pages/detail/detail';
...

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    DetailPage
  ],
  ...
  entryComponents: [
    MyApp,
    HomePage,
    DetailPage
  ],
DetailPage也會使用DataProvider的資料存取服務,同樣要import DataProvider,並在constructor中設定DataProvider引數。detail.ts程式碼如下:
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { DataProvider } from '../../providers/data/data';

@Component({
  selector: 'page-detail',
  templateUrl: 'detail.html',
})
export class DetailPage {
  id=0;
  data={};
  constructor(public navCtrl: NavController, public navParams: NavParams,
    public ds: DataProvider
  ) {
    this.id = this.navParams.get('id');
    this.data = this.ds.getItem(this.id);
  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad DetailPage');
  }
}

  1. 第2,12行:和HomePage不同在於DetailPage要接收傳送過來的參數,因此要import NavParams元件,並在constructor中設定NavParams引數,透過該引數,才能讀取前一頁面傳送過來的參數。
  2. 第15行:NavParams物件提供get()函式,指定屬性名稱,例如get('id'),便能讀取HomePage傳送參數中的id欄位值。
  3. 第16行:取得DATA陣列中this.id值所指定的陣列元素。
detail.html以ion-card製作卡片式頁面,可參考ionic官網文件,關於ion-card的說明。detail.html程式碼如下:
<ion-header>
  <ion-navbar>
    <ion-title> {{data.name}} </ion-title>
  </ion-navbar>
</ion-header>
<ion-content padding>
  <ion-card>
    <ion-item>
      <ion-icon name="wine" item-left large></ion-icon>
      <h3> {{ data.id}} {{data.name}}</h3>
      <p>{{ data.Desc }}</p>
    </ion-item>
    <ion-item>
      <span item-left>股價{{data.price}}</span>
      <span item-left>本益比{{data.PER}}</span>
    </ion-item>

  </ion-card>

</ion-content>

最後,完整專案可參考https://github.com/leuowang/ionic-App.git裡之listApp資料夾。

沒有留言:

張貼留言