本文使用Ionic2從頭建立一個(gè)簡單的Todo應(yīng)用,讓用戶可以做以下事情:
- 查看todo列表
- 添加新的todo項(xiàng)
- 查看todo詳情
- 保存 todo到持久化存儲
0 開始之前
本教程需要你了解基本的Ionic 2概念。已經(jīng)在電腦上安裝了Ionic 2。如果沒有,先去安裝和學(xué)習(xí)吧。
1 創(chuàng)建新的Ionic 2工程
我們將通過生成一個(gè)基于“空白”模板的新項(xiàng)目開始。這是一個(gè)空的項(xiàng)目框架,但有一些示例代碼供我們使用。
運(yùn)行以下命令創(chuàng)建新項(xiàng)目
ionic start ionic-todo blank --v2
一旦代碼生成,在文本編輯器打開項(xiàng)目??梢钥吹絀onic 2項(xiàng)目的基本結(jié)構(gòu), 這些是由Ionic CLI生成的代碼。
基本上,我們的應(yīng)用程序中的所有組件(我們的應(yīng)用程序?qū)⒂刹煌慕M件組成)將在** src ** 文件夾中(包括app文件夾中的根組件和在pages文件夾中我們所有的頁面組件)。一個(gè)組件將包括一個(gè)模板(.html文件),類定義(.ts文件),或者一些樣式(.scss文件)。同組件類似,您還可能創(chuàng)建諸如服務(wù)services(如稍后我們將創(chuàng)建的數(shù)據(jù)服務(wù)),但沒有模板和樣式,但在結(jié)構(gòu)上類似一個(gè)正常的組件。這些服務(wù)也被稱作“providers”將被放置在一個(gè)providers文件夾。
現(xiàn)在,只有一個(gè)HomePage組件,設(shè)置一個(gè)虛擬視圖。在我們的應(yīng)用程序中我們要修改這個(gè)來顯示的所有待辦事項(xiàng)列表。
先從自動生成的**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)。相比其他組件該組件是特殊的,因?yàn)樗堑谝粋€(gè)組件被加載到應(yīng)用程序,從那里我們可以顯示更多的組件,可以添加更多的組件等等?;旧?我們的應(yīng)用程序結(jié)構(gòu)就像一棵樹,根組件就是樹的根。
因此,重要的是我們的根組件(root component)知道在哪里可以找到我們的HomePage主頁,因?yàn)樾枰獙⑺O(shè)置為root page根頁面。注意,我們導(dǎo)入(importing)HomePage** 在這個(gè)文件主頁的頂部,然后在下面的代碼中設(shè)置它作為根頁面(** root page**):
rootPage: any = HomePage;
我們可以在構(gòu)造函數(shù)上面聲明變量,像上面這樣的使其成員變量 member variables,這意味著他們可以通過引用this.myVal在整個(gè)類中被被訪問,同時(shí),它也將在您的模板中可用。** : any ** 只是一個(gè)TypeScript語言的內(nèi)容,意味著rootPage可以是任何(any)類型。如果你不適應(yīng) TypeScript,并感到困惑,那也不用擔(dān)心——你可以把類型拋開,您的應(yīng)用程序仍然會工作的很好。我不會在本教程中使用類型,除了依賴注入是不可替代的地方(我們將稍后介入)。如果你想知道更多關(guān)于在Ionic 2中使用類型,應(yīng)該學(xué)習(xí)TypeScript或ECMAScript 6相關(guān)知識。
root page 根頁面是您應(yīng)用程序顯示的第一個(gè)頁面,然后你可以從這里導(dǎo)航到其他頁面。改變Ionic 2應(yīng)用程序中的視圖可以通過改變這一根頁面,或** push ** 推或 pop彈出視圖。推一個(gè)視圖將會改變展現(xiàn),彈出它將刪除當(dāng)前視圖并回到前面的視圖。關(guān)于導(dǎo)航的更詳細(xì)的解釋,我推薦看看一個(gè)相關(guān)的Ionic 2導(dǎo)航指南。
2. 設(shè)置主頁(Home page)
現(xiàn)在我們已經(jīng)建立了基本的應(yīng)用程序,讓故事開始吧。首先,讓我們建立todo列表模板。
2.1 創(chuàng)建模板
按照下面的內(nèi)容修改 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,創(chuàng)建了一個(gè)速記到嵌入的模板中。這樣就不用迭代輸出了:
<ion-item *ngFor="let item of items" (click)="viewItem(item)">{{item.title}}</ion-item>
根據(jù)DOM(文檔對象模型),嵌入式模板將會為每個(gè)項(xiàng)(items)創(chuàng)建特定的數(shù)據(jù)。所以,如果我們的items數(shù)組(稍后將定義在類定義)有4項(xiàng),那么< ion-item >將渲染四次。還要注意,我們使用的** let item ,循環(huán)分配一個(gè)items數(shù)組項(xiàng)給item**。這允許我們引用其屬性,并傳遞到viewItem函數(shù)。
我們將標(biāo)題設(shè)置為Todos(待辦事項(xiàng))!我們設(shè)計(jì)一個(gè)按鈕使用< ion-buttons >。因?yàn)檫@里有個(gè)end屬性,按鈕將被放置在end的位置。不同屬性的行為可能會有所不同,取決于在什么平臺上運(yùn)行,以iOS為例,將end會將按鈕放到導(dǎo)航欄的右邊。還要注意,按鈕本身我們給它一個(gè)屬性的ion-button將會使用Ionic 2 的按鈕樣式,而icon-only樣式將會讓按鈕只包含一個(gè)圖標(biāo)沒有文本。
我們使用** (click) ** 來附加一個(gè)點(diǎn)擊監(jiān)聽器到這個(gè)元素,這里將在在home.ts中調(diào)用addItem()函數(shù)。
2.2 創(chuàng)建類
按如下修改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(){
}
}
還記得大明湖畔的夏雨荷嗎?哦不是,還記得之前我們?nèi)绾谓ohomePage分配一個(gè)any類型變量嗎?現(xiàn)在我們在構(gòu)造函數(shù)中分配一個(gè)NavController類型給navCtrl參數(shù)。這就是Ionic 2 的依賴注入工作模式,基本上是一種方式告訴應(yīng)用程序“我們希望通過navCtrl引用到NavController”。通過添加公共關(guān)鍵字在它面前,它會自動創(chuàng)建一個(gè)成員變量。這意味著我們現(xiàn)在可以引用NavController通過在類里任意使用this.navCtrl。
現(xiàn)在我們已經(jīng)建立了一些假的數(shù)據(jù)(我們使用ionViewDidLoad生命周期鉤子,這將在頁面加載時(shí)被觸發(fā)),您應(yīng)該能夠看到它已經(jīng)在列表中渲染了:

在運(yùn)行** ionic serve ** 時(shí),因?yàn)榧热晃覀儗?dǎo)入了NavController服務(wù),我們就可以在這個(gè)組件push或pop視圖,如下所示:
this.navCtrl.push(SOME_PAGE);
或者
ionic g page AddItemPage
我們已經(jīng)創(chuàng)建了添加和查看項(xiàng)目的方法,在更進(jìn)一步之前我們不得不先創(chuàng)建 AddItemPage andItemDetailPage 組件。
2.3 添加項(xiàng)目
我們將要?jiǎng)?chuàng)建一個(gè)新組件讓我們添加新的todo項(xiàng)。當(dāng)然,這只是一個(gè)簡單的表單提供了標(biāo)題和描述來創(chuàng)建todo。
運(yùn)行如下命令來生成一個(gè)add-item頁面
ionic g page AddItemPage
任何時(shí)候當(dāng)我們創(chuàng)建一個(gè)新頁面,我們需要確保該頁面被導(dǎo)入(imported)到我們的 app.module.ts,然后在entryComponents和declarations數(shù)組中被聲明。
按如下修改 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 {}
就像上次,我們先創(chuàng)建組件的模版。
2.4 建立新增項(xiàng)目模版
按如下內(nèi)容修改 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>
這里沒有什么太瘋狂的開始。這次我們定義了另一個(gè)按鈕,簡單地調(diào)用了定義在add-item-page.ts中的saveItem函數(shù)。我們還有另一個(gè)按鈕指向一個(gè)close方法——因?yàn)檫@個(gè)頁面作為一個(gè)Mode模式的頁面,我們希望能把頁面關(guān)閉,所以我們也會在add-item-page.ts定義這個(gè)方法。
現(xiàn)在我們有一些輸入框了,它們又有[(ngModel)]屬性,這個(gè)就是雙向綁定。任何作用到title字段的改變都將立即影響到add-tiem-page.ts(我們馬上要講到)里面的this.title成員變量。反之亦然,任何this.title上的改變都將立即影響到模版。
同樣注意到我們的保存按鈕上使用了full屬性,這個(gè)方便的小屬性幫助我們設(shè)置按鈕寬度為full。
2.5 建立添加項(xiàng)的類
現(xiàn)在我們將要建立一個(gè)類給我們的添加項(xiàng)組件。
按如下內(nèi)容修改 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();
}
}
這里我們導(dǎo)入了一個(gè)怪異的服務(wù):ViewController,可以用于模態(tài)(Modals)頁面的關(guān)閉(dismiss)。
除此之外,我們創(chuàng)建了saveItem函數(shù)來創(chuàng)建newItem對象,它使用當(dāng)前的標(biāo)題和描述值(即我們建立雙向數(shù)據(jù)綁定,無論用戶輸入什么),然后我們關(guān)閉視圖,同時(shí)我們也傳入了newItem在dismiss方法中。這將允許我們建立一個(gè)偵聽器,當(dāng)回到主頁(就是那個(gè)啟動這個(gè)頁面的另外一個(gè)頁面)時(shí)獲取數(shù)據(jù)。通過這種方式,我們可以從一個(gè)頁面?zhèn)鬟f數(shù)據(jù)到另一個(gè)頁面(然而,記住,模態(tài)不需要在頁面之間傳遞數(shù)據(jù))。
2.6 在主頁保存新增項(xiàng)
就像我提到的,我們把要保存的數(shù)據(jù)返回發(fā)送給HomePage。我們現(xiàn)在導(dǎo)入import我們新增的AddItemPage組件到HomePage,當(dāng)用戶點(diǎn)擊新增時(shí)我們就創(chuàng)建出該視圖。
按如下內(nèi)容修改 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){
}
}
你看上面這個(gè)文件的頂部,可以發(fā)現(xiàn)我們現(xiàn)在導(dǎo)入import了AddItemPage組件。這時(shí)我們就可以用這個(gè)頁面創(chuàng)建模態(tài)頁面了,具體看addItem方法。注意我們這里建立了一個(gè)onDidDismiss監(jiān)聽器,這樣就可以獲取模態(tài)關(guān)閉時(shí)回傳的數(shù)據(jù),并通過saveItem方法保存?,F(xiàn)在,我們僅通過將數(shù)據(jù)push到items數(shù)組,最終,我們將保存到數(shù)據(jù)庫。
我們已經(jīng)移除了假數(shù)據(jù),因?yàn)楝F(xiàn)在用戶輸入通過saveItem方法被添加到了this.items。我們將items初始為空。
2.7 查看項(xiàng)目
現(xiàn)在,我們想要一個(gè)功能,就是用戶點(diǎn)擊todo列表里面的某一項(xiàng),然后可以看到該項(xiàng)的細(xì)節(jié)信息(例如:這里只有描述可以看了,實(shí)際可以根據(jù)需要擴(kuò)展,呵呵)。要做這個(gè)我們應(yīng)該知道這是又要?jiǎng)?chuàng)建一個(gè)新組件了啊。
還記得如何創(chuàng)建頁面嗎,運(yùn)行下面的代碼創(chuàng)建一個(gè) item-detail 頁面:
ionic g page ItemDetailPage
time and time again,我們需要在 app.module.ts 文件中設(shè)置一下,三件事: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 {}
按照順序是該寫模版了,開始:
千篇一律,按照下面的內(nèi)容自行修改 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>
相比其他模版,這里相當(dāng)?shù)闹卑住N覀冎皇鞘褂?lt; ion-card >指令簡單裝飾下,并輸出標(biāo)題和描述,值將在item-detail-page.ts中定義。
好,繼續(xù)按照下面的內(nèi)容自行修改 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;
}
}
當(dāng)我們把這個(gè)頁面將傳入的數(shù)據(jù)項(xiàng),點(diǎn)擊,然后我們把物品的標(biāo)題和描述,使用NavParams。
現(xiàn)在我們要做的是在home.ts 內(nèi)設(shè)置 viewItem 函數(shù)和導(dǎo)入新的細(xì)節(jié)頁面。
src/pages/home/home.ts 修改如下:
viewItem(item){
this.navCtrl.push(ItemDetailPage, {
item: item
});
}
添加的導(dǎo)入代碼放在 src/pages/home/home.ts 的頂部:
import { ItemDetailPage } from '../item-detail-page/item-detail-page';
這時(shí)就可以push出項(xiàng)目的細(xì)節(jié)頁面,然后傳入被點(diǎn)擊的項(xiàng)目。如果你現(xiàn)在點(diǎn)擊存在于列表中的項(xiàng)目,你可能看到如下界面:

3 持久化數(shù)據(jù)保存
Todo應(yīng)用程序現(xiàn)在將基本工作,但數(shù)據(jù)沒有被存儲在任何地方只要你刷新應(yīng)用程序你將失去你所有的數(shù)據(jù)(不理想)。
現(xiàn)在我們要做的是創(chuàng)建一個(gè)服務(wù)被稱為Data用來處理存儲和檢索數(shù)據(jù)。我們將使用Ionic 2提供的Stroage服務(wù)來幫助我們做到這一點(diǎn)。Stroage服務(wù)是Ionic 2的通用存儲服務(wù),它負(fù)責(zé)存儲數(shù)據(jù)的最佳方式,同時(shí)提供了一致的API供我們使用。
這意味著,如果您正在設(shè)備上運(yùn)行,安裝了SQLite插件,那么它將使用一個(gè)本地SQLite數(shù)據(jù)庫進(jìn)行存儲,否則它將退回到使用基于瀏覽器的存儲(可能被操作系統(tǒng)擦除)。
運(yùn)行下面代碼創(chuàng)建服務(wù)
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);
}
}
這個(gè)是有點(diǎn)不同于我們已經(jīng)創(chuàng)建的組件(它可能更合適認(rèn)為是service)。我們不使用@component裝飾,而使用@Injectable聲明這個(gè)類。
在構(gòu)造函數(shù)中,我們建立一個(gè) Storage 服務(wù)的引用。
數(shù)組中save函數(shù)簡單地將所有的項(xiàng)放入數(shù)組并保存到存儲,每當(dāng)項(xiàng)目變化我們將調(diào)用這個(gè)函數(shù)。
我們還將需要設(shè)置的Storage服務(wù),以及 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 {}
請注意,我們已經(jīng)聲明這些在providers的數(shù)組,而不是declarations或entryComponents數(shù)組。
現(xiàn)在我們需要更新。ts使用這項(xiàng)新服務(wù)。
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數(shù)據(jù)服務(wù),通過傳遞給構(gòu)造函數(shù)。我們依然設(shè)置 items 開始是空的,使用數(shù)據(jù)服務(wù)獲取數(shù)據(jù)。
重要的是要注意getData 返回promise而不是數(shù)據(jù)本身。抓取的數(shù)據(jù)存儲是異步的,這意味著我們的應(yīng)用程序?qū)⒗^續(xù)運(yùn)行當(dāng)數(shù)據(jù)加載時(shí)。promise讓我們數(shù)據(jù)完成加載時(shí)執(zhí)行一些操作,而不需要暫停整個(gè)應(yīng)用程序。
最后,我們還添加一個(gè)調(diào)用save 函數(shù)保存在數(shù)據(jù)服務(wù)當(dāng)一個(gè)新的條目被添加?,F(xiàn)在該函數(shù)將馬上更新我們的新數(shù)據(jù)條目數(shù)組,但items也將被復(fù)制保存到數(shù)據(jù)服務(wù),以便下次我們回到應(yīng)用程序是可用。
4 總結(jié)
在本教程中我們已經(jīng)介紹了如何實(shí)現(xiàn)很多Ionic 2應(yīng)用的常用功能:
- 創(chuàng)建視圖
- 監(jiān)聽和處理事件
- 視圖之間的導(dǎo)航
- 在視圖之間傳遞數(shù)據(jù)
- 建立雙向數(shù)據(jù)綁定
- 保存數(shù)據(jù)
顯然還有很多我們可以做,使這個(gè)應(yīng)用程序更漂亮,添加刪除和編輯筆記的能力等等。