使用Ionic2開發Todo應用

本文使用Ionic2從頭建立一個簡單的Todo應用,讓用戶可以做以下事情:

  • 查看todo列表
  • 添加新的todo項
  • 查看todo詳情
  • 保存 todo到持久化存儲

0 開始之前

本教程需要你瞭解基本的Ionic 2概念。已經在電腦上安裝了Ionic 2。如果沒有,先去安裝和學習吧。

1 創建新的Ionic 2工程

我們將通過生成一個基於“空白”模板的新項目開始。這是一個空的項目框架,但有一些示例代碼供我們使用。

運行以下命令創建新項目

ionic start ionic-todo blank --v2

一旦代碼生成,在文本編輯器打開項目。可以看到Ionic 2項目的基本結構, 這些是由Ionic CLI生成的代碼。

基本上,我們的應用程序中的所有組件(我們的應用程序將由不同的組件組成)將在 src 文件夾中(包括app文件夾中的根組件和在pages文件夾中我們所有的頁面組件)。一個組件將包括一個模板(.html文件),類定義(.ts文件),或者一些樣式(.scss文件)。同組件類似,您還可能創建諸如服務services(如稍後我們將創建的數據服務),但沒有模板和樣式,但在結構上類似一個正常的組件。這些服務也被稱作“providers”將被放置在一個providers文件夾。

現在,只有一個HomePage組件,設置一個虛擬視圖。在我們的應用程序中我們要修改這個來顯示的所有待辦事項列表。

先從自動生成的**src/app/app.component.ts文件開始來看一下:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from 'ionic-native';

import { HomePage } from '../pages/home/home';

@Component({
  template: `<ion-nav [root]="rootPage"></ion-nav>`
})
export class MyApp {
  rootPage = HomePage;

  constructor(platform: Platform) {
    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      StatusBar.styleDefault();
    });
  }
}

app.component.ts文件中定義了根組件(root component)。相比其他組件該組件是特殊的,因爲它是第一個組件被加載到應用程序,從那裏我們可以顯示更多的組件,可以添加更多的組件等等。基本上,我們的應用程序結構就像一棵樹,根組件就是樹的根。

因此,重要的是我們的根組件(root component)知道在哪裏可以找到我們的HomePage主頁,因爲需要將它設置爲root page根頁面。注意,我們導入(importing)HomePage 在這個文件主頁的頂部,然後在下面的代碼中設置它作爲根頁面( root page**):

rootPage: any = HomePage;

我們可以在構造函數上面聲明變量,像上面這樣的使其成員變量 member variables,這意味着他們可以通過引用this.myVal在整個類中被被訪問,同時,它也將在您的模板中可用。 : any 只是一個TypeScript語言的內容,意味着rootPage可以是任何(any)類型。如果你不適應 TypeScript,並感到困惑,那也不用擔心——你可以把類型拋開,您的應用程序仍然會工作的很好。我不會在本教程中使用類型,除了依賴注入是不可替代的地方(我們將稍後介入)。如果你想知道更多關於在Ionic 2中使用類型,應該學習TypeScript或ECMAScript 6相關知識。
root page 根頁面是您應用程序顯示的第一個頁面,然後你可以從這裏導航到其他頁面。改變Ionic 2應用程序中的視圖可以通過改變這一根頁面,或 push 推或 pop彈出視圖。推一個視圖將會改變展現,彈出它將刪除當前視圖並回到前面的視圖。關於導航的更詳細的解釋,我推薦看看一個相關的Ionic 2導航指南。

2. 設置主頁(Home page)

現在我們已經建立了基本的應用程序,讓故事開始吧。首先,讓我們建立todo列表模板。

2.1 創建模板

按照下面的內容修改 src/pages/home/home.html :

<ion-header>
  <ion-navbar color="secondary">
    <ion-title>
        Todos!
    </ion-title>
    <ion-buttons end>
        <button ion-button icon-only (click)="addItem()"><ion-icon name="add-circle"></ion-icon></button>
    </ion-buttons>
  </ion-navbar>
</ion-header>
 <ion-content>
  <ion-list>
    <ion-item *ngFor="let item of items" (click)="viewItem(item)">{{item.title}}</ion-item>
  </ion-list>
</ion-content>

注意這裏使用的語法在列表中使用*ngFor*,創建了一個速記到嵌入的模板中。這樣就不用迭代輸出了:

<ion-item *ngFor="let item of items" (click)="viewItem(item)">{{item.title}}</ion-item>

根據DOM(文檔對象模型),嵌入式模板將會爲每個項(items)創建特定的數據。所以,如果我們的items數組(稍後將定義在類定義)有4項,那麼< ion-item >將渲染四次。還要注意,我們使用的 let item ,循環分配一個items數組項給item。這允許我們引用其屬性,並傳遞到viewItem函數。

我們將標題設置爲Todos(待辦事項)!我們設計一個按鈕使用< ion-buttons >。因爲這裏有個end屬性,按鈕將被放置在end的位置。不同屬性的行爲可能會有所不同,取決於在什麼平臺上運行,以iOS爲例,將end會將按鈕放到導航欄的右邊。還要注意,按鈕本身我們給它一個屬性的ion-button將會使用Ionic 2 的按鈕樣式,而icon-only樣式將會讓按鈕只包含一個圖標沒有文本。

我們使用 (click) 來附加一個點擊監聽器到這個元素,這裏將在在home.ts中調用addItem()函數。

2.2 創建類

按如下修改src/pages/home/home.ts

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
 @Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
   public items;
   constructor(public navCtrl: NavController) {
   }
   ionViewDidLoad(){
     this.items = [
      {title: 'hi1', description: 'test1'},
      {title: 'hi2', description: 'test2'},
      {title: 'hi3', description: 'test3'}
    ];
   }
   addItem(){
   }
   viewItem(){
   }
 }

還記得大明湖畔的夏雨荷嗎?哦不是,還記得之前我們如何給homePage分配一個any類型變量嗎?現在我們在構造函數中分配一個NavController類型給navCtrl參數。這就是Ionic 2 的依賴注入工作模式,基本上是一種方式告訴應用程序“我們希望通過navCtrl引用到NavController”。通過添加公共關鍵字在它面前,它會自動創建一個成員變量。這意味着我們現在可以引用NavController通過在類裏任意使用this.navCtrl。

現在我們已經建立了一些假的數據(我們使用ionViewDidLoad生命週期鉤子,這將在頁面加載時被觸發),您應該能夠看到它已經在列表中渲染了:


Todos 列表頁面


在運行 ionic serve 時,因爲既然我們導入了NavController服務,我們就可以在這個組件pushpop視圖,如下所示:

this.navCtrl.push(SOME_PAGE);

或者

ionic g page AddItemPage

我們已經創建了添加和查看項目的方法,在更進一步之前我們不得不先創建 AddItemPage andItemDetailPage 組件。

2.3 添加項目

我們將要創建一個新組件讓我們添加新的todo項。當然,這只是一個簡單的表單提供了標題描述來創建todo。

運行如下命令來生成一個add-item頁面

ionic g page AddItemPage

任何時候當我們創建一個新頁面,我們需要確保該頁面被導入(imported)到我們的 app.module.ts,然後在entryComponents和declarations數組中被聲明。

按如下修改 src/app/app.module.ts :

import { NgModule } from '@angular/core';
import { IonicApp, IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AddItemPage } from '../pages/add-item-page/add-item-page';
 @NgModule({
  declarations: [
    MyApp,
    HomePage,
    AddItemPage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    AddItemPage
  ],
  providers: []
})
export class AppModule {}

就像上次,我們先創建組件的模版。

2.4 建立新增項目模版

按如下內容修改 src/pages/add-item-page/add-item-page.html :

<ion-header>
  <ion-toolbar color="secondary">
    <ion-title>
        Add Item
    </ion-title>
        <ion-buttons end>
            <button ion-button icon-only (click)="close()"><ion-icon name="close"></ion-icon></button>
        </ion-buttons>
    </ion-toolbar>
</ion-header>
 <ion-content>
    <ion-list>
       <ion-item>
        <ion-label floating>Title</ion-label>
        <ion-input type="text" [(ngModel)]="title"></ion-input>
      </ion-item>
       <ion-item>
        <ion-label floating>Description</ion-label>
        <ion-input type="text" [(ngModel)]="description"></ion-input>
      </ion-item>
     </ion-list>
     <button full ion-button color="secondary" (click)="saveItem()">Save</button> 
 </ion-content>

這裏沒有什麼太瘋狂的開始。這次我們定義了另一個按鈕,簡單地調用了定義在add-item-page.ts中的saveItem函數。我們還有另一個按鈕指向一個close方法——因爲這個頁面作爲一個Mode模式的頁面,我們希望能把頁面關閉,所以我們也會在add-item-page.ts定義這個方法。
現在我們有一些輸入框了,它們又有[(ngModel)]屬性,這個就是雙向綁定。任何作用到title字段的改變都將立即影響到add-tiem-page.ts(我們馬上要講到)裏面的this.title成員變量。反之亦然,任何this.title上的改變都將立即影響到模版。
同樣注意到我們的保存按鈕上使用了full屬性,這個方便的小屬性幫助我們設置按鈕寬度爲full。

2.5 建立添加項的類

現在我們將要建立一個類給我們的添加項組件。

按如下內容修改 add-item-page.ts

import { Component } from '@angular/core';
import { NavController, ViewController } from 'ionic-angular';
 @Component({
  selector: 'page-add-item-page',
  templateUrl: 'add-item-page.html'
})
export class AddItemPage {
   title;
  description;
   constructor(public navCtrl: NavController, public view: ViewController) {
   }
   saveItem(){
     let newItem = {
      title: this.title,
      description: this.description
    };
     this.view.dismiss(newItem);
   }
   close(){
    this.view.dismiss();
  }
 }

這裏我們導入了一個怪異的服務:ViewController,可以用於模態(Modals)頁面的關閉(dismiss)。

除此之外,我們創建了saveItem函數來創建newItem對象,它使用當前的標題描述值(即我們建立雙向數據綁定,無論用戶輸入什麼),然後我們關閉視圖,同時我們也傳入了newItem在dismiss方法中。這將允許我們建立一個偵聽器,當回到主頁(就是那個啓動這個頁面的另外一個頁面)時獲取數據。通過這種方式,我們可以從一個頁面傳遞數據到另一個頁面(然而,記住,模態不需要在頁面之間傳遞數據)。

2.6 在主頁保存新增項

就像我提到的,我們把要保存的數據返回發送給HomePage。我們現在導入import我們新增的AddItemPage組件到HomePage,當用戶點擊新增時我們就創建出該視圖。

按如下內容修改 src/pages/home/home.ts :

import { Component } from '@angular/core';
import { ModalController, NavController } from 'ionic-angular';
import { AddItemPage } from '../add-item-page/add-item-page'
 @Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
   public items = [];
   constructor(public navCtrl: NavController, public modalCtrl: ModalController) {
   }
   ionViewDidLoad(){
   }
   addItem(){
     let addModal = this.modalCtrl.create(AddItemPage);
     addModal.onDidDismiss((item) => {
           if(item){
            this.saveItem(item);
          }
     });
     addModal.present();
   }
   saveItem(item){
    this.items.push(item);
  }
   viewItem(item){
   }
 }

你看上面這個文件的頂部,可以發現我們現在導入importAddItemPage組件。這時我們就可以用這個頁面創建模態頁面了,具體看addItem方法。注意我們這裏建立了一個onDidDismiss監聽器,這樣就可以獲取模態關閉時回傳的數據,並通過saveItem方法保存。現在,我們僅通過將數據push到items數組,最終,我們將保存到數據庫。
我們已經移除了假數據,因爲現在用戶輸入通過saveItem方法被添加到了this.items。我們將items初始爲空。

2.7 查看項目

現在,我們想要一個功能,就是用戶點擊todo列表裏面的某一項,然後可以看到該項的細節信息(例如:這裏只有描述可以看了,實際可以根據需要擴展,呵呵)。要做這個我們應該知道這是又要創建一個新組件了啊。

還記得如何創建頁面嗎,運行下面的代碼創建一個 item-detail 頁面:

ionic g page ItemDetailPage

time and time again,我們需要在 app.module.ts 文件中設置一下,三件事:import,declarations, entryComponents:

import { NgModule } from '@angular/core';
import { IonicApp, IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AddItemPage } from '../pages/add-item-page/add-item-page';
import { ItemDetailPage } from '../pages/item-detail-page/item-detail-page';
 @NgModule({
  declarations: [
    MyApp,
    HomePage,
    AddItemPage,
    ItemDetailPage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    AddItemPage,
    ItemDetailPage
  ],
  providers: []
})
export class AppModule {}

按照順序是該寫模版了,開始:

千篇一律,按照下面的內容自行修改 src/pages/item-detail-page/item-detail-page.html :

<ion-header>
  <ion-navbar color="secondary">
    <ion-title>
      {{title}}
    </ion-title>
  </ion-navbar>
</ion-header>
<ion-content>
  <ion-card>
    <ion-card-content>
      {{description}}
    </ion-card-content>
  </ion-card>
</ion-content>

相比其他模版,這裏相當的直白。我們只是使用< ion-card >指令簡單裝飾下,並輸出標題和描述,值將在item-detail-page.ts中定義。

好,繼續按照下面的內容自行修改 src/pages/item-detail-page/item-detail-page.ts :

import { Component } from '@angular/core';
import { NavParams } from 'ionic-angular';
@Component({
  selector: 'page-item-detail-page',
  templateUrl: 'item-detail-page.html'
})
export class ItemDetailPage { 
  title;
  description; 
  constructor(public navParams: NavParams){ 
  } 
  ionViewDidLoad() {
    this.title = this.navParams.get('item').title;
    this.description = this.navParams.get('item').description;
  } 
}

當我們把這個頁面將傳入的數據項,點擊,然後我們把物品的標題和描述,使用NavParams。

現在我們要做的是在home.ts 內設置 viewItem 函數和導入新的細節頁面。

src/pages/home/home.ts 修改如下:

viewItem(item){
  this.navCtrl.push(ItemDetailPage, {
    item: item
  });
}

添加的導入代碼放在 src/pages/home/home.ts 的頂部:

import { ItemDetailPage } from '../item-detail-page/item-detail-page';

這時就可以push出項目的細節頁面,然後傳入被點擊的項目。如果你現在點擊存在於列表中的項目,你可能看到如下界面:


項目細節頁面

3 持久化數據保存

Todo應用程序現在將基本工作,但數據沒有被存儲在任何地方只要你刷新應用程序你將失去你所有的數據(不理想)。

現在我們要做的是創建一個服務被稱爲Data用來處理存儲和檢索數據。我們將使用Ionic 2提供的Stroage服務來幫助我們做到這一點。Stroage服務是Ionic 2的通用存儲服務,它負責存儲數據的最佳方式,同時提供了一致的API供我們使用。

這意味着,如果您正在設備上運行,安裝了SQLite插件,那麼它將使用一個本地SQLite數據庫進行存儲,否則它將退回到使用基於瀏覽器的存儲(可能被操作系統擦除)。

運行下面代碼創建服務

ionic g provider Data

data.ts 代碼修改如下:

import { Storage } from '@ionic/storage';
import {Injectable} from '@angular/core'; 
@Injectable()
export class Data { 
  constructor(public storage: Storage){ 
  } 
  getData() {
    return this.storage.get('todos');  
  } 
  save(data){
    let newData = JSON.stringify(data);
    this.storage.set('todos', newData);
  } 
}

這個是有點不同於我們已經創建的組件(它可能更合適認爲是service)。我們不使用@component裝飾,而使用@Injectable聲明這個類。

在構造函數中,我們建立一個 Storage 服務的引用。

數組中save函數簡單地將所有的項放入數組並保存到存儲,每當項目變化我們將調用這個函數。

我們還將需要設置的Storage服務,以及 Data provider,在我們 app.module.ts 文件。

src/app/app.module.ts 修改如下:

import { NgModule } from '@angular/core';
import { IonicApp, IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AddItemPage } from '../pages/add-item-page/add-item-page';
import { ItemDetailPage } from '../pages/item-detail-page/item-detail-page';
import { Storage } from '@ionic/storage';
import { Data } from '../providers/data'; 
@NgModule({
  declarations: [
    MyApp,
    HomePage,
    AddItemPage,
    ItemDetailPage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    AddItemPage,
    ItemDetailPage
  ],
  providers: [Storage, Data]
})
export class AppModule {}

請注意,我們已經聲明這些在providers的數組,而不是declarationsentryComponents數組。

現在我們需要更新。ts使用這項新服務。

src/pages/home/home.ts 文件修改如下:

import { Component } from '@angular/core';
import { ModalController, NavController } from 'ionic-angular';
import { AddItemPage } from '../add-item-page/add-item-page'
import { ItemDetailPage } from '../item-detail-page/item-detail-page';
import { Data } from '../../providers/data'; 
@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage { 
  public items = []; 
  constructor(public navCtrl: NavController, public modalCtrl: ModalController, public dataService: Data) { 
    this.dataService.getData().then((todos) => { 
      if(todos){
        this.items = JSON.parse(todos); 
      } 
    }); 
  } 
  ionViewDidLoad(){ 
  } 
  addItem(){ 
    let addModal = this.modalCtrl.create(AddItemPage); 
    addModal.onDidDismiss((item) => { 
          if(item){
            this.saveItem(item);
          } 
    }); 
    addModal.present(); 
  } 
  saveItem(item){
    this.items.push(item);
    this.dataService.save(this.items);
  } 
  viewItem(item){
    this.navCtrl.push(ItemDetailPage, {
      item: item
    });
  } 
}

這是我們最後的一些代碼。再次,我們importing數據服務,通過傳遞給構造函數。我們依然設置 items 開始是空的,使用數據服務獲取數據。
重要的是要注意getData 返回promise而不是數據本身。抓取的數據存儲是異步的,這意味着我們的應用程序將繼續運行當數據加載時。promise讓我們數據完成加載時執行一些操作,而不需要暫停整個應用程序。

最後,我們還添加一個調用save 函數保存在數據服務當一個新的條目被添加。現在該函數將馬上更新我們的新數據條目數組,但items也將被複制保存到數據服務,以便下次我們回到應用程序是可用。

4 總結

在本教程中我們已經介紹瞭如何實現很多Ionic 2應用的常用功能:

  • 創建視圖
  • 監聽和處理事件
  • 視圖之間的導航
  • 在視圖之間傳遞數據
  • 建立雙向數據綁定
  • 保存數據

顯然還有很多我們可以做,使這個應用程序更漂亮,添加刪除和編輯筆記的能力等等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章