Ionic2入門教程 實現TodoList App-2 實現TodoList App

mavlarn發表於2017-04-20

在上一篇教程Ionic2入門教程 實現TodoList App-1 初識Ionic2 中,我們介紹了Ionic2框架和使用Ionic2的命令列工具建立一個專案,並介紹了建立的專案結構和各個部分。在這一部分教程中,我們還是來實現一個TodoList的例項,來看看怎麼用Ionic2開發一個web應用。

本教程的原始碼,可以從github上獲得,感興趣的同學可以結合著原始碼閱讀以下內容。

最終實現的效果還是跟之前的教程Angular2入門教程-2 實現TodoList App 實現的類似,最終結果如下:

Ionic2入門教程 實現TodoList App-2 實現TodoList App

為了方便,大部分的頁面和樣式,都直接沿用了之前的例項裡的內容。只是上面使用了ionic提供的header元件,header裡面新增了一個按鈕,跳轉到about頁面。

在開始編碼之前,我們還是先看看這個簡單的應用的元件結構圖:

Ionic2入門教程 實現TodoList App-2 實現TodoList App

為了演示模組化開發的方式,我們還是把這個簡單的應用分成2個模組,主模組和todo模組。主模組裡面包含根元件app.component,和 about元件。todo模組包含了3個todo的元件:list, itemdetail。在實際上的專案開發中,模組化開發是非常重要的。如果不是用模組化的開發,即使我們的元件都是獨立的,也需要都在app模組裡面註冊,這樣多人協作開發時,很容易在app模組檔案上出現衝突。而且每個模組裡面的增加刪除頁面,都需要更新主模組,使得專案各元件無法解耦。

主模組

首先,我們還是從主模組入手。在 上一部分的教程中,我們簡單介紹了Ionic2的主模組app module和app.component,現在再來詳細看看加入了新的todo模組以後,主模組該如何編寫。

app.module

首先,看app.module.ts的內容:

import { NgModule, ErrorHandler } from '@angular/core';
import { IonicModule, IonicApp, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { TodoModule } from '../pages/todo/todo.module';
import { AboutComponent } from '../pages/about/about.component';

@NgModule({
  declarations: [ MyApp, AboutComponent ],
  imports: [ IonicModule.forRoot(MyApp), TodoModule ],
  bootstrap: [IonicApp],
  entryComponents: [ MyApp, AboutComponent ],
  providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}]
})
export class AppModule {}複製程式碼

在一開始建立的專案模板中,是這樣的:

  declarations: [ MyApp, HomePage ],
  imports: [ IonicModule.forRoot(MyApp) ],
  bootstrap: [IonicApp],
  entryComponents: [ MyApp, HomePage],
  // providers以及後面的省略複製程式碼

可以看到,主要的區別就是imports的部分,因為我們新加了一個模組,就需要在這裡把新模組加入進來。這樣就不需要在主模組中把所有的子模組的所有的元件都一個個的在這裡註冊。
然後,我們也在這裡的bootstrap中設定了這個應用的根元件是通過IonicModule.forRoot(MyApp)引入的元件IonicApp

app.component

然後再看根元件,在app.component.ts裡,定義了一個元件MyApp,也就是應用的根元件,內容如下:

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

import { TodoListComponent } from '../pages/todo/list/list.component';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage = TodoListComponent;

  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();
      Splashscreen.hide();
    });
  }
}複製程式碼

它定義了這個元件的模板是app.html,在建構函式裡,通過Platform元件來控制app的狀態列的樣式,以及關閉app的載入頁。這個只是在打包成app的時候才起作用。
StatusBar是一個cordova的外掛,用於控制應用在開啟時的狀態列的樣式、顏色,如是否透明等。
Splashscreen是app的載入頁的外掛。這裡呼叫它的hide方法確保載入頁被關閉。
而所有cordova的外掛的方法,都必須在platform.ready()的回撥裡面呼叫,也就是在應用開啟後,cordova底層的框架初始化完成以後,再去呼叫相應的外掛的方法。

如果你想針對你的web應用做一些環境的初始化之類的操作,也應該在這裡實現。例如,你的web應用既想打包成app,又想通過手機瀏覽器提供訪問,這樣的話,你就需要設定你的api的路徑。就可以在這個ready方法裡面這樣實現:

  if (this.platform.is('cordova')) { // 有cordova表示是app
    // 在app裡,需要指定api伺服器的地址
    AppConstants.apiHost = 'http://some.server.com/api'
  } else { // 沒有則說明是web方式部署
    // 在web方式裡,api伺服器的地址,和部署這個web應用的地址一樣。
    AppConstants.apiHost = ''
  }複製程式碼

app.component元件的定義中,我們沒有看到樣式的定義,但是在資料夾中又可以看到有一個app.scss的檔案。實際上,Ionic2在編譯打包的時候,預設就是查詢跟模板名一樣,字尾是.scss的樣式檔案,來作為模板樣式。所以,我們在開發Ionic應用的時候也應該使用scss的樣式會比較方便。

雖然很多框架都使用sass(或scss)來定義樣式,但是,在國內基於npm使用sass,非常的不方便,主要是因為qiang的問題。編譯sass(或scss)都要使用node-sass庫,而這個庫又依賴lib-sassnode-gyp等庫,而這些庫的安裝,又需要本地編譯,需要gcc或微軟的.net環境等。所以,這些庫不能直接複製過來使用,必須安裝,而國內的網路環境訪問npm資源庫經常會非常慢。雖然國內有taobao的映象,但是在本地編譯安裝上面說的庫的時候,還是得從國外的地址下載。(至少在我之前安裝的時候是這樣) 。所以,想搞前端開發的,還是先弄好科學上網的環境,不然會非常麻煩。

迴歸正題,再看看模板app.html裡面的內容:

<ion-nav [root]="rootPage"></ion-nav>複製程式碼

這裡使用了一個ionic的導航器元件ion-nav。在Ionic2中,它沒有使用Angular2的路由模組,而是實現了一套自己的導航模組。它跟Angular2的路由的功能類似,但是在用法上又有很大的區別。我們先直接看怎麼用,在本教程的最後再總結它和Angular2路由模組的區別。簡單來說,在根元件的模板中,有一個導航器,那麼,這個導航器的控制器就幫我們來實現從這個根元件開始的頁面的前進(載入)和後退。
這裡,使用[root]="rootPage"進行資料繫結,也就是從元件component到頁面view的單向繫結。然後在MyApp裡,看到rootPage = TodoListComponent;。關聯到一起就是,我們把TodoListComponent元件作為這個web應用的第一個頁面(root頁面),賦給導航器。那麼,我們的應用在開啟根元件app.component的時候,就會載入第一個頁面元件TodoListComponent

對於繫結不瞭解的,可以看看另一篇有關 Angular2架構淺析 的教程。

about.component

這個元件沒什麼說的,就是一個簡單的頁面,顯示一段話。模板內容如下:

<ion-header>
  <ion-navbar>
    <ion-title>關於</ion-title>
  </ion-navbar>
</ion-header>
<ion-content>
  <p>About angular2-basic</p>
  <p>Just a simple example project to get started with Angular. Now includes routes!</p>
</ion-content>複製程式碼

我們使用了ion-header來新增一個header,裡面有一個ion-navbar,標題是關於。由於我們使用Ionic2提供的導航器,所以,當我們從別的頁面進入這個頁面的時候,header的左側就會出現一個後退按鈕。點選後退按鈕以後,就會退到上一頁。這就是Ionic2提供的導航器的優點:自動判斷歷史記錄,自動顯示或隱藏後退按鈕,並且實現後退功能。

todo模組

todo模組的定義也很簡單,就是把裡面使用的各個元件和服務註冊一下:

import { NgModule }       from '@angular/core';
import { IonicModule } from 'ionic-angular';

import { MyApp } from '../../app/app.component';
import { TodoListComponent }    from './list/list.component';
import { TodoDetailComponent }  from './detail/detail.component';
import { TodoItemComponent }  from './item/item.component';
import { TodoService } from './todo.service';

@NgModule({
  imports:      [IonicModule.forRoot(MyApp)],
  declarations: [TodoListComponent, TodoDetailComponent, TodoItemComponent],
  entryComponents:[TodoListComponent, TodoDetailComponent],
  providers:    [TodoService],
  exports:      [IonicModule]
})
export class TodoModule {}複製程式碼

需要注意的是,在Ionic2中,所有的需要加到導航器裡顯示的頁面元件,必須加到entryComponents裡。如果只是一個modal,那就不需要。在這個例子中,TodoItem是todo列表的一個子元件,它不會被單獨顯示到頁面上,所以不需要加到entryComponents裡。
還有,所有的服務元件需要加到providers裡,跟Angular2一樣。

TodoService和Todo

TodoService和Todo跟之前的例項完全一樣,Todo是待辦事宜的model定義,TodoService裡面定義了一系列的增刪改查方法。

TodoListComponent

接下來看這個列表元件,也就是開啟app的時候,根元件裡面導航器里載入的根頁面。還是先直接看程式碼:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Todo } from '../todo';
import { TodoService } from '../todo.service';
import { AboutComponent } from "../../about/about.component";

@Component({
    selector: 'todo-list',
    templateUrl: 'list.component.html'
})
export class TodoListComponent {
    newTodo: Todo = new Todo();
    constructor(public navCtrl: NavController, private todoService: TodoService) { }

    addTodo() {
        if (!this.newTodo.title.trim()) {
            return;
        }
        this.todoService.addTodo(this.newTodo);
        this.newTodo = new Todo();
    }
    get todos() {
        return this.todoService.getAllTodos();
    }
    gotoAbout() {
      this.navCtrl.push(AboutComponent)
    }
}複製程式碼

TodoListComponent裡,有一個成員變數newTodo,型別是Todo,用來新建一個待辦事宜的時候儲存新建的資料。
在建構函式裡,我們使用Angular2的依賴注入,注入了2個服務:NavControllerTodoService。後一個不用說,就是用來操作業務資料的。NavController就是Ionic2導航器的控制器。

TodoListComponent裡,header裡面的右上角有一個按鈕可以跳轉到about頁面,所以,這個元件裡面有一個方法gotoAbout(),它用this.navCtrl.push(AboutComponent)開啟一個about頁面。
下面就是列表元件的模板:

<ion-header>
  <ion-navbar>
    <ion-title>Todo List</ion-title>
    <ion-buttons end>
      <button ion-button icon-only (click)="gotoAbout()">
        <ion-icon name="construct"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>
<ion-content>
  <ion-card>
    <section class="todoapp">
      <header class="header">
        <input class="new-todo" placeholder="Get things done!" autofocus="" [(ngModel)]="newTodo.title" (keyup.enter)="addTodo()">
      </header>
      <section class="main" *ngIf="todos.length > 0">
        <ul class="todo-list">
          <todo-item *ngFor="let todo of todos" [todo]="todo">
          </todo-item>
        </ul>
      </section>
      <footer class="footer" *ngIf="todos.length > 0">
        <span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span>
      </footer>
    </section>
  </ion-card>
</ion-content>複製程式碼

這個模板裡,上面有一個header,下面的內容跟之前的例項一樣,它的解釋請參考之前的教程Angular2入門教程-2 實現TodoList App
例項中所有的css都是在相應的.scss檔案中,具體內容請參考例項原始碼

Ionic2中,所有的頁面跳轉都由導航控制器來實現,它有一些方法,其中常用的有:

  • push(page, params, opts)
    用來往導航器的棧里加一個元件,也就是前進到一個頁面。
  • pop(opts)
    就是從導航器的棧裡彈出一頁,也就是後退一頁。
  • setRoot(page, params, opts)
    這個用來設定跟頁面,設定跟頁面以後,歷史記錄會被清空,也就無法後退。

Ionic2的導航器還有一些hooks,你可以用來定義在導航器載入某個元件的時候呼叫的方法,例如,ionViewCanEnter,你可以通過在一個元件裡實現這個方法,來控制使用者是否能夠進入這個頁面。例如對於todo list頁面,我們控制使用者需要登入才能開啟,大概可以這樣實現:

export class TodoListComponent {
  // 省略其他...
  ionViewCanEnter(): Promise<boolean> {
    console.log('in home component can enter.')
    return this.authService.isLoggedIn().map(isLogin => {
      if (isLogin) {
        console.log('ionView Home Can Enter.')
        return true
      } else {
        console.log('ionView Home Cannot Enter.')
        this.navCtrl.push(SigninPage)
        return false
      }
    }).toPromise()
  }
}複製程式碼

它需要返回一個boolean型別的值,或者一個boolean型別的promise。我這裡是在一個authService裡判斷使用者是否登入,而我的所有service返回的都是Observable,所以我把它在轉換成promise返回。

其他的方法和詳細說明可以參考官方文件

TodoItemComponent

這個也不需要多說,就是列表元件的一個子元件,顯示每一條待辦事宜。具體的還是請參考之前的教程。只有一個不一樣的地方就是,點選一個item項跳轉到詳情頁。這裡還是用的NavController,具體如下:

gotoDetail(todo) {
    this.navCtrl.push(TodoDetailComponent, {todoId: todo.id})
}複製程式碼

在這裡跳轉的時候傳了一個引數todoId

TodoDetailComponent

詳情頁的元件也很簡單,在元件初始化的時候,獲取傳過來的引數todoId,並用這個引數從todoService裡獲得todo。然後模板就會將todo資料顯示到頁面上。

export class TodoDetailComponent implements OnInit {
    selectedTodo: Todo;
    constructor(public navParams: NavParams, private todoService: TodoService) {}
    ngOnInit() {
      let todoId = this.navParams.get('todoId')
      this.selectedTodo = this.todoService.getTodoById(todoId);
    }
}複製程式碼

編譯和打包

最後,再提一下編譯打包的問題,這裡說的打包,是將TypeScript的檔案編譯併合並打包成main.js,所有的css頁打包成main.css。對於使用cordova將應用打包成app,也沒什麼可說的,就是用它的命令打包就可以,網上的相關教程也很多。
Ioni2給我們提供了一套編譯打包的指令碼,並把它們放在單獨的庫裡:github.com/driftyco/io… 然後在ionic的專案的package.json裡,加入這個依賴就可以使用。它給我們提供了很多現成的指令碼,用於執行多種任務,例如編譯、監聽檔案修改、並啟動開發伺服器,打包成main.js和main.css,minify等。
通過命令列工具生成的專案裡,提供了4個執行命令:

  "scripts": {
    "clean": "ionic-app-scripts clean",
    "build": "ionic-app-scripts build",
    "ionic:build": "ionic-app-scripts build",
    "ionic:serve": "ionic-app-scripts serve"
  },複製程式碼

ionic buildnpm run build執行的是ionic-app-scripts build,就是執行編譯,生成main.js和main.css。
除了這幾個預設提供的以外,常用的還有一個:

"min": "ionic-app-scripts minify",複製程式碼

可以通過npm run min執行。執行這個之前,需要先執行`npm run build,也就是main.js和main.css檔案必須存在,然後,min這個命令就會執行一系列的程式碼混淆、壓縮等指令碼,將js和css都進行壓縮。在這篇教程的例項專案中,壓縮前main.js是5.5M左右,壓縮後是1.3M。

總結

上面,就是一個比較完整的Ionic2 app的開發的過程,如果你需要從伺服器獲取資料,可以使用Angular2的http模組,使用方法也一樣。剩下的,就是了解Ionic2提供的樣式元件和服務元件,特別是服務元件的使用,需要仔細閱讀官方文件。然後,基本上你就可以用Ionic2開發應用了,不管是web應用、混合app。你用習慣Ionic以後,就會發現,開發app會變得非常簡單。

Ionic2是完全基於Angular2,它的很多概念和用法,也都是來自於Angular2。但是,也有一些不一樣,最主要的可能就是路由了。在上面說到,Ionic2使用自己的導航器元件來實現路由和導航。它跟Angular2的路由模組相比,主要的區別有:

  1. Ionic2的導航器不是基於url的,也就是說,預設情況下,導航不會引起位址列的變化,你也無法通過在位址列輸入一個url,來直接進入一個元件頁面。但是,Ionic2又提供了一個DeepLinker的元件,來實現url和導航器的結合。你需要在模組定義裡面,用IonicModule引入根元件的時候,定義元件和路徑的對應關係。如下:

    imports: [
    IonicModule.forRoot(MyApp, {backButtonText: ''}, {
      links: [
        { component: Home, name: 'home', segment: 'home' },
        { component: SigninPage, name: 'sign', segment: 'sign' },
        { component: TodoListPage, name: 'list', segment: 'list', defaultHistory: [Home] },
        { component: TodoDetailPage, name: 'detail', segment: 'detail', defaultHistory: [Home] }
      ]
    }),
    HttpModule
    ],複製程式碼

    具體用法請參考官方文件。

  2. Ionic2的導航器無法巢狀,你不能像Angular2的路由一樣,通過巢狀的路由來實現一個模組中的頁面、子頁面等功能。所以也無法通過巢狀的路由來實現許可權控制。例如,有一套路由/account, /account/wallet/account/address。如果使用Angular2的路由,我們可以在/account這個路由的定義上加一個Guard,來控制只有使用者登入了才能訪問/account和它所有的子路徑。但是Ionic2的導航器就無法實現這種控制。

  3. Angular2的路由是一個服務,整個應用裡面應該只有一個路由配置。雖然你可以通過子模組、延時載入模組來實現多個路由配置。但是從效果上來說,他們應該也是一起構成一套路由配置。但是Ionic2裡面的導航器就是一個控制器類,以及配套的navbar導航欄。舉個例子來說,在一個典型的多個tab頁面的應用中,通常,有關頁面直接導航跳轉是設計是這樣:在每個tab頁裡面,你可以進入一個個頁面,後退的時候,又是一頁一頁後退到這個tab的第一個頁面。你不應該能後退到進入這個tab之前的那個tab頁。也就是說,在每個tab裡面,有一個導航歷史的棧,在一個tab裡面的前進後退不會影響另一個tab頁裡的前進後退。這雖然不是什麼強制的標準,但是也是從ionic1開始就遵守的模式。在Ionic2中,對於這種情況,它在每個tab頁裡建立了一個導航器,每個導航器控制自己tab頁內的前進後退。如果你使用了它提供了導航欄,是否顯示後退按鈕、以及點選後退的行為都是已經實現好的。

相關文章