上一節(jié)課我們做好了所有的準(zhǔn)備工作旗唁,我們現(xiàn)在就要開始進(jìn)行實(shí)際創(chuàng)作了。本課我們將聚焦于Home頁(yè)來(lái)把它改造成用來(lái)展示GIF回饋的列表頁(yè)。
Reddit Provider
我們將要?jiǎng)?chuàng)建的布局嚴(yán)重的依賴從reddit拉取的數(shù)據(jù)源葫,我們決定了創(chuàng)建一個(gè)提供者來(lái)為我們做這些操作。我們現(xiàn)在不會(huì)進(jìn)入reddit提供者的實(shí)現(xiàn)細(xì)節(jié)砖瞧,但是我們現(xiàn)在得設(shè)置好我們最終將要用到的函數(shù)息堂,這樣可以在本課中用上他們。
> 修改 src/providers/reddit.ts為如下:
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
@Injectable()
export class Reddit {
settings: any;
loading: boolean = false;
posts: any = [];
subreddit: string = 'gifs';
page: number = 1;
perPage: number = 15;
after: string;
stopIndex: number;
sort: string = 'hot'
moreCount: number = 0;
constructor(public http: Http) {
}
fetchData(): void {
}
}
這幾本書就是這個(gè)提供者的股價(jià)块促,他定義了大量的成員變量和fetchData函數(shù)用于從reddit獲取數(shù)據(jù)荣堰。成員函數(shù)的作用是什么現(xiàn)在可能不是很明顯,我們還是(按順序)了解一下吧:
- 用戶提供的設(shè)置settings
- 是否加載了當(dāng)前新的GIF
- 當(dāng)前加載完成的所有的GIF的入口
- 當(dāng)前的subreddit
- 當(dāng)前頁(yè)(即竭翠,用戶點(diǎn)擊“Load More”的次數(shù))
- 每頁(yè)展示的GIF的數(shù)量
- 最后一個(gè)從Reddit拉取的帖子的引用(這樣我們就知道下次從哪個(gè)頁(yè)面開始)
- 用于存儲(chǔ)帖子數(shù)組的長(zhǎng)度
- GIF排列依據(jù)
- 應(yīng)用會(huì)持續(xù)向reddit拉取數(shù)據(jù)直到夠一頁(yè)的數(shù)據(jù)振坚,moreCount告訴它加載更多是具體多少(即,如果在請(qǐng)求API20次還不夠的話)
我們希望盡快完成這個(gè)提供者斋扰,但是現(xiàn)在我們還是先完成home頁(yè)面布局渡八。
布局
在開始制作這個(gè)布局之前,也就是home.html传货,我們先看一下效果圖:
這是一個(gè)很簡(jiǎn)單的布局屎鳍,頂部一個(gè)導(dǎo)航條,包含一個(gè)搜索條一個(gè)設(shè)置按鈕(用于啟動(dòng)Settings頁(yè)面)问裕。在他下面是一系列的從Reddit返回的GIF列表逮壁。還有截屏里面沒有顯示的列表底部的‘Load More’按鈕,用戶點(diǎn)擊這個(gè)按鈕的時(shí)候?qū)?huì)加載下一頁(yè)的GIF粮宛。
首先我們看一下整個(gè)模板貌踏,然后分成各個(gè)小塊來(lái)詳細(xì)講解。
> 修改 src/pages/home/home.html為如下:
<ion-header>
<ion-navbar color="secondary">
<ion-title>
<ion-searchbar color="primary" placeholder="enter subreddit name..."></ion-searchbar>
</ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="openSettings()"><ion-icon name="settings"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item no-lines>
GIF GOES HERE
</ion-item>
<ion-list-header>
TITLE GOES HERE
</ion-list-header>
<ion-item *ngIf="redditService.loading" no-lines style="text-align:center;">
<img src="assets/images/loader.gif" style="width: 50px" />
</ion-item>
</ion-list>
<button ion-button full color="light" (click)="loadMore()">Load More...</button>
</ion-content>
第一部分設(shè)置了導(dǎo)航條:
<ion-navbar color="secondary">
<ion-title>
<ion-searchbar color="primary" placeholder="enter subreddit name..."></ion-searchbar>
</ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="openSettings()"><ion-icon name="settings"></ion-icon></button>
</ion-buttons>
</ion-navbar>
我們給<ion-navbar>添加了secondary是屬性這樣后續(xù)會(huì)將他樣式調(diào)整到使用secondary顏色窟勃。
我們用了<ion-title>祖乳,這個(gè)組件通常是用來(lái)在navbar中展示標(biāo)題的,這里我們用來(lái)將搜索條調(diào)整到navbar的居中位置秉氧。通常每個(gè)搜索條會(huì)有一個(gè)單獨(dú)的工具欄這樣它會(huì)占用整個(gè)空間眷昆,但是我不想讓屏幕看起來(lái)很亂所以這里用它來(lái)稍微調(diào)整一下。為了能夠讓他恰當(dāng)?shù)娜スぷ鳎覀兒罄m(xù)會(huì)添加一些自定義樣式亚斋。我們也給搜索條提供了primary屬性這樣他會(huì)用primary顏色作媚。
然后,我們使用<ion-buttons>來(lái)防止我們的設(shè)置按鈕帅刊,這個(gè)按鈕會(huì)啟動(dòng)設(shè)置頁(yè)面纸泡。使用end指令可以將按鈕放到右邊,如果想把按鈕放到左邊的話那么就用start指令赖瞒。我們也給按鈕添加了一個(gè)(click)監(jiān)聽器這樣在點(diǎn)擊按鈕的時(shí)候會(huì)調(diào)用openSettings函數(shù)女揭。我們現(xiàn)在沒有創(chuàng)建這個(gè)函數(shù)所以現(xiàn)在點(diǎn)擊的時(shí)候什么都不會(huì)發(fā)生,但是我們稍后會(huì)在home.ts中定義栏饮。
接下來(lái)我們定義了主題內(nèi)容區(qū)域:
<ion-list>
<ion-item no-lines>
GIF GOES HERE
</ion-item>
<ion-list-header>
TITLE GOES HERE
</ion-list-header>
<ion-item *ngIf="loading" no-lines style="text-align: center;">
<img src="assets/images/loader.gif" style="width: 50px" />
</ion-item>
</ion-list>
列表是移動(dòng)應(yīng)用中使用最多的組件之一吧兔。Ionic中你可以通過創(chuàng)建一個(gè)<ion-list>然后給其中添加任意數(shù)量的<ion-item>來(lái)創(chuàng)建一個(gè)列表。目前我們只有一個(gè)項(xiàng)袍嬉,后續(xù)將改為自動(dòng)循環(huán)每個(gè)需要顯示的GIF境蔼。同時(shí)我們也用到了<ion-list-header>來(lái)創(chuàng)建一個(gè)頭來(lái)展示GIF的標(biāo)題,同時(shí)也給項(xiàng)添加了no-lines屬性這樣列表項(xiàng)不會(huì)有邊緣顯示了伺通。
跟GIF項(xiàng)一樣箍土,我們將在列表的底部添加一個(gè)額外的項(xiàng),其中包含了一個(gè)加載動(dòng)畫罐监。他將用于在獲取新的GIF的過程中顯示一個(gè)旋轉(zhuǎn)動(dòng)畫吴藻,但是由于他只會(huì)在發(fā)生加載的時(shí)候顯示,我們使用了 *ngIf 指令來(lái)控制他的顯示時(shí)機(jī)笑诅。本案例中调缨,加載動(dòng)畫只會(huì)在loading為true的時(shí)候才顯示(這個(gè)值會(huì)后面在類中定義疮鲫,然后在加載前和加載完成后對(duì)他進(jìn)行更改)
模板中剩下的代碼是一個(gè)加載更多按鈕:
<button ion-button full color="light" (click)="loadMore()">Load More...</button>
這里沒什么驚心動(dòng)魄的東西吆你,給這個(gè)組件提供了一個(gè)light指令以改變他的色值,有一個(gè)(click)函數(shù)設(shè)置點(diǎn)擊的時(shí)候調(diào)用類定義中的loadMore()函數(shù)俊犯。
類定義
在模板定義里我們解決了我們的“視圖”妇多,現(xiàn)在現(xiàn)在需要去制作類定義來(lái)處理列表頁(yè)的邏輯。這里用于定義模板里面引用到的所有函數(shù)燕侠,以及其他一些頁(yè)面運(yùn)行的代碼者祖。
同理,我們先貼出所有代碼然后一點(diǎn)一點(diǎn)的來(lái)解釋绢彤。
> 修改 src/pages/home/home.ts 為如下:
import { Component } from '@angular/core';
import { ModalController, Platform } from 'ionic-angular';
import { Keyboard } from 'ionic-native';
import { SettingsPage } from '../settings/settings';
import { Data } from '../../providers/data';
import { Reddit } from '../../providers/reddit';
import { FormControl } from '@angular/forms';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
subredditValue: string;
constructor(public dataService: Data, public redditService: Reddit, public modalCtrl: ModalController, public platform: Platform) {
}
ionViewDidLoad(){
this.platform.ready().then(() => {
this.loadSettings();
});
}
loadSettings(): void {
console.log("TODO: Implement loadSettings()");
}
showComments(post): void {
console.log("TODO: Implement showComments()");
}
openSettings(): void {
console.log("TODO: Implement openSettings()");
}
playVideo(e, post): void {
console.log("TODO: Implement playVideo()");
}
changeSubreddit(): void {
console.log("TODO: Implement changeSubreddit()");
}
loadMore(): void {
console.log("TODO: Implement loadMore()");
}
}
顯然新加的代碼不少七问,即使只是基本類定義看起來(lái)也挺復(fù)雜的。我們先走一遍茫舶。
首先是import語(yǔ)句:
import { Component } from '@angular/core';
import { ModalController, Platform } from 'ionic-angular';
import { Keyboard } from 'ionic-native';
import { SettingsPage } from '../settings/settings';
import { Data } from '../../providers/data';
import { Reddit } from '../../providers/reddit';
import { FormControl } from '@angular/forms';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
這里有很多導(dǎo)入械巡,因?yàn)槲覀儼押罄m(xù)會(huì)用到的所有東西都導(dǎo)入進(jìn)來(lái)了,但是對(duì)于后面大部分案例來(lái)講,這算是少的了讥耗。
前面的非秤泄矗基礎(chǔ),ModalController允許我們創(chuàng)建一個(gè)模態(tài)頁(yè)展示到當(dāng)前頁(yè)面的頂部(譯者:不是頁(yè)面的頂部古程,是覆蓋整個(gè)頁(yè)面的上面)蔼卡,Platform允許我們?cè)O(shè)備加載完成之后執(zhí)行一些操作。之后挣磨,我們導(dǎo)入了Keyboard插件(稍后使用)雇逞。
我們也導(dǎo)入了之前創(chuàng)建的SettingsPage(目前還沒完成),導(dǎo)入了Data提供者(當(dāng)前也沒完善)趋急,還有未完成的Reddit提供者喝峦。FormControl允許我們?yōu)檩斎肟騽?chuàng)建一個(gè)“FormControl”(讓我們可以訪問Observable)。rxjs導(dǎo)入的是RxJS庫(kù) -- 令人煩惱的是呜达,你需要自己導(dǎo)入Observable的操作符谣蠢,所以接下來(lái)導(dǎo)入了。操作符的使用在使用的時(shí)候再討論查近。
接下來(lái)的是構(gòu)造器constructor函數(shù)和ionViewDidLoad函數(shù)眉踱。構(gòu)造器函數(shù)的類的重要組成部分,因?yàn)樗穷悓?shí)例化的時(shí)候第一個(gè)執(zhí)行的函數(shù)霜威。在其中我們可以注入和設(shè)置需要用到的組件或者服務(wù)谈喳,也是想要立即執(zhí)行一些函數(shù)的最佳點(diǎn)。始終記住戈泼,最佳體驗(yàn)是不要在構(gòu)造器里面做太多的“工作”婿禽,這樣一些代碼你可以放到ionViewDidLoad()周期函數(shù)里面去(這個(gè)函數(shù)在頁(yè)面加載完成之后第一個(gè)執(zhí)行)。
subredditValue: string;
constructor(public dataService: Data, public redditService: Reddit, public modalCtrl: ModalController, public platform: Platform) {
}
ionViewDidLoad(){
this.platform.ready().then(() => {
this.loadSettings();
});
}
在ionViewDidLoad中我們?cè)O(shè)置了注入服務(wù)的引用(我們的Data和Reddit)大猛,在構(gòu)造器的頂部我們?cè)O(shè)置了一個(gè)成員變量用于綁定到模塊中的搜索條的輸入框扭倾。我們也調(diào)用了loadSettings()函數(shù)將會(huì)從存儲(chǔ)中加載用戶設(shè)置。重點(diǎn)是我們是在平臺(tái)準(zhǔn)備好之后再調(diào)用的挽绩。
我們來(lái)看看剩下的代碼:
loadSettings(): void {
console.log("TODO: Implement loadSettings()");
}
showComments(post): void {
console.log("TODO: Implement showComments()");
}
openSettings(): void {
console.log("TODO: Implement openSettings()");
}
playVideo(e, post): void {
console.log("TODO: Implement playVideo()");
}
changeSubreddit(): void {
console.log("TODO: Implement changeSubreddit()");
}
loadMore(): void {
console.log("TODO: Implement loadMore()");
}
剩下的只是一大堆的函數(shù)膛壹。這些方法要么是模板調(diào)用的,要么是里面其他地方調(diào)用的(構(gòu)造器或者其他方法)唉堪。很明顯他們現(xiàn)在都是白板模聋,后面會(huì)對(duì)他們進(jìn)行擴(kuò)展。
使用Observable來(lái)控制搜索
我們接下來(lái)用Observable唠亚。我們已經(jīng)在基礎(chǔ)部分詳細(xì)探討過什么是Observable链方,如果你忘得差不多了的話,建議現(xiàn)在回去讀一遍獲取數(shù)據(jù)灶搜,Observable和Promise部分祟蚀。
大部分Observable的使用就是簡(jiǎn)單的訂閱Observable的返回值共啃,例如使用Http服務(wù)獲取數(shù)據(jù)(這個(gè)應(yīng)用的下一部分就會(huì)用到了)。所以基本你不用自己創(chuàng)建一個(gè)Observable暂题。但是如果你想要的話移剪,還是可以用得上他提供的一些其他的方法。
我們將用到之前導(dǎo)入的FormControl服務(wù)來(lái)創(chuàng)建一個(gè)“FormControl”這樣就會(huì)給咱們提供一個(gè)Observable薪者。FormControl跟[(ngModel)]提供的創(chuàng)想數(shù)據(jù)綁定的工作方式很像纵苛,他們都是將類定義中的變量和模板中的輸入域捆綁到一起。接下來(lái)我們做一些變更言津。
> 修改 src/pages/home/home.html 為如下:
<ion-searchbar color="primary" placeholder="enter subreddit name..." [(ngModel)]="subredditValue" [formControl]="subredditControl" value=""></ion-searchbar>
我們這里所作只是添加了一個(gè)[formControl]攻人,他將提供給稍后創(chuàng)建的Control使用。接下來(lái)悬槽,我們來(lái)對(duì)類的構(gòu)造器做一些改動(dòng)怀吻。
> 修改 src/pages/home/home.ts 的constructor和ionViewDidLoad為如下:
subredditValue: string;
subredditControl: FormControl;
constructor(public dataService: Data, public redditService: Reddit, public modalCtrl: ModalController, public platform: Platform) {
this.subredditControl = new FormControl();
}
ionViewDidLoad(){
this.subredditControl.valueChanges.debounceTime(1500)
.distinctUntilChanged().subscribe(subreddit => {
if(subreddit != '' && subreddit){
this.redditService.subreddit = subreddit;
this.changeSubreddit();
Keyboard.close();
}
});
this.platform.ready().then(() => {
this.loadSettings();
});
}
首先我們創(chuàng)建了一個(gè)新的FormControl,然后訂閱了他提供的valueChangesObservable初婆。如果你看過基礎(chǔ)部分的Observable的話蓬坡,那么這里看起來(lái)沒什么奇怪的。我們訂閱subscribe了observable這樣每次他提交新的值的時(shí)候都會(huì)運(yùn)行這段代碼磅叛。這里比較奇怪的地方是valueChanges.debounceTime(1500).distinctUntilChanged()屑咳。基本上弊琴,我們可以任意鏈接需要數(shù)量的操作符(因?yàn)槊總€(gè)函數(shù)都是返回的Observable兆龙,所以還是可以訂閱)他們每一個(gè)的做的事情都不一樣。例如敲董,當(dāng)我們這么做的話:
this.subredditControl.valueChanges.debounceTime(1500).subscribe
我們只有在輸入發(fā)生變更且1.5秒內(nèi)沒有新的輸入的話才運(yùn)行代碼紫皇。這防止我們頻繁提交無(wú)意義的請(qǐng)求到API。例如腋寨,當(dāng)用戶輸入‘chemicalreactiongifs’的時(shí)候聪铺,代碼將會(huì)觸發(fā)‘c’,然后‘ch’精置,然后‘che’计寇,然后‘chem’等等直到完成的單詞輸入完成锣杂。他不止是頻繁向發(fā)送無(wú)用的查詢脂倦,帶來(lái)的是結(jié)果列表頻繁刷新導(dǎo)致極差的用戶體驗(yàn)。通過添加彈性時(shí)間元莫,代碼只會(huì)在完整的字符串輸入完成之后運(yùn)行一次(假設(shè)用戶字符之間輸入間隔用不了1.5秒)赖阻。
最后,我們添加了最終的操作符:
this.subredditControl.valueChanges.debounceTime(1500).distinctUntilChanged().subscribe
這樣只有值和上次的值不一樣的時(shí)候才會(huì)運(yùn)行代碼踱蠢。所以當(dāng)用戶輸入‘gifs’火欧,點(diǎn)擊回退鍵將改成‘gif’棋电,然后又假設(shè)‘s’將他變成‘gifs’,什么事情都不會(huì)發(fā)生苇侵。我們不需要重新為‘gifs’加載數(shù)據(jù)赶盔,因?yàn)樗呀?jīng)是了。
最終結(jié)果是當(dāng)值發(fā)生改變的時(shí)候代碼才會(huì)觸發(fā)榆浓,用戶現(xiàn)在還沒有輸入于未,輸入值和之前的一樣。觸發(fā)的代碼只是簡(jiǎn)單的檢查是否提供了一個(gè)空值陡鹃,然后通過更改Reddit的subreddit成員變量烘浦,調(diào)用他的changeSubreddit()函數(shù)來(lái)改變活躍的subreddit。我們也調(diào)用了Keyboaid插件的close()方法萍鲸;由于我們做的事情違反了輸入域的規(guī)則闷叉,鍵盤不知道何時(shí)關(guān)閉,所以我們手動(dòng)來(lái)關(guān)閉脊阴。
這可能是應(yīng)用最迷惑的部分握侧,特別是當(dāng)你剛知道Observable的情況下。你也知道他允許我們很輕松的創(chuàng)建一些很有用的功能嘿期,但是我們也可以很簡(jiǎn)單的使用普通 ngModel 方式和使用一個(gè)按鈕來(lái)觸發(fā)搜索行為來(lái)替換他藕咏。
總結(jié)
本課中我們完成了一個(gè)非常好的開頭,我們?cè)谥谱饕恍┍容^酷的這條路上也漸漸上到秽五。我們也有了一個(gè)很好的基礎(chǔ)架構(gòu)這樣允許我們?cè)谙乱徽n中在它只是輕松的制作功能孽查。如果你現(xiàn)在通過以下命令運(yùn)行應(yīng)用的時(shí)候:
ionic serve
應(yīng)該可以看到這樣的效果圖:
好丑哇。甚至你還可以在控制臺(tái)中看到一些錯(cuò)誤坦喘。相信我盲再,最后一切都會(huì)解決的!下一棵將會(huì)從reddit拉取一些真實(shí)數(shù)據(jù)瓣铣。