0 開始之前
通過本教程之前,您應該至少了解一些基本的Ionic 2概念。您還必須已經安裝了Ionic 2 在您的機器上。
1 創建一個新的Ionic 2 應用
我們將使用有Ionic團隊創建的tutorial模板,可見于官方教程,來創建我們的應用程序。要做到這一點,您需要運行以下命令:
ionic start ionic2-tutorial tutorial --v2
現在您的應用程序將自己開始建立。為運行后續的命令,你應當將項目目錄作為當前工作目錄:
cd ionic2-tutorial
簡單瞟一眼應用效果,使用serve命令:
ionic serve
上面也說了,這些命令應該在當前項目目錄下執行。
2 目錄結構
如果你看看生成的文件和文件夾,這一切看起來非常類似于一個Ionic 1最初的應用程序。這也是一個非常典型的科Cordova風格項目結構。
如果你再看看在src 文件夾,事情開始看起來有點不同:
通常在一個Ionic 1應用程序中,人們所有的Javascript文件(控制器、服務等)在一個文件夾中,所有的模板在另一個文件夾,然后所有的樣式包含在一個app.scss文件中。
Ionic 2應用程序的默認結構通過功能的組織,因此一個特定組件(在上面的示例中我們有一個基本的頁面組件,組件列表,和一個項目詳細信息組件)的所有邏輯、模板和樣式都在一起。這是Angular 2方法論的完美應用,一切都是獨立的組件,這些組件可以很容易地在其他地方或項目中重用。如果你想重復使用一個特定的功能,或有很多人工作在同一個項目中,舊的Ionic 1方法會變得非常麻煩。
根據功能組織代碼的想法不是Angular 2 & Ionic 2 的特權,事實上人們在Ionic 1中使用和倡導基于特征的方式,只是大多數人沒那樣做(趨勢是很難打破)。通過Angular 2 的工作方式,默認就使用基于特征的結構,因此不難推行這種結構。
index.html
已經是慣例了,瀏覽器第一個打開的文件就是 index.html 。因此我們先來看看Ionic 2中是怎樣的:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<title>Ionic App</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
<link rel="manifest" href="assets/manifest.json">
<meta name="theme-color" content="#4e8ef7">
<!-- un-comment this code to enable service worker
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js')
.then(() => console.log('service worker installed'))
.catch(err => console.log('Error', err));
}
</script>-->
<link href="build/main.css" rel="stylesheet">
</head>
<body>
<!-- Ionic's root component and where the app will load -->
<ion-app></ion-app>
<!-- cordova.js required for cordova apps -->
<script src="cordova.js"></script>
<!-- The polyfills js is generated during the build process -->
<script src="build/polyfills.js"></script>
<!-- The bundle js is generated during the build process -->
<script src="build/main.js"></script>
</body>
</html>
這看上去非常簡潔,和Ionic 1沒有什么不同。這里最大的不同是沒用附加ng-app 到body標簽(目的是是讓Ionic知道應用存在的地方),而是使用了:
<ion-app></ion-app>
根組件將在這里被創建,通常你的入口應用在這里注入。cordova.js的引用讓我們可以使用Cordova創建應用(將應用打包為native應用,可以提交到App Store),polyfill.js是為瀏覽器某些特點功能的基本補丁,main.js是我們應用綁定的代碼。
基本上,這看起來就是一個非常普通的網頁。
assets
這個assets目錄用于保存你工程里面使用的靜態文件,就像圖片、JSON數據文件等等。任何這個文件夾下的東西都會在應用程序每次build編譯時覆蓋拷貝到你的build目錄。
theme
這個 theme 目錄包含了你應用程序中的 global.scss 和variables.scss 文件。多數你應用中的樣式是通過使用每個組件自己的 .scss 文件,但是你可以使用 global.scss 文件定義任何自定義樣式,通過不同的方式,你也可以修改 variables.scss 文件中的 SASS 變量來修改你應用的樣式。
app
所有的Ionic 2 App 都有 root component。這不是和你應用里面其他組件的差別,一個明顯的差別是它在自己的 app 文件夾中,而且被命名為 app.component.ts。
如果你有一點不確定 component 到底是個什么東西,我們具體來看看:
@Component({
templateUrl: 'my-component.html'
})
export class Something {
// ...snip
}
這就是一個具有** something 功能的組件,技術上來說component關聯一個視圖,否則這個類可能考慮為services更好。不管是component還是servece,創建都差不多,都可以被導入import到你的應用中。
根組件root component是第一個被加載的,接下來我們看看root component是怎么定義和工作的。我們先看看整個文件,然后分解說明:
import { Component, ViewChild } from '@angular/core';
import { Platform, MenuController, Nav } from 'ionic-angular';
import { StatusBar } from 'ionic-native';
import { HelloIonicPage } from '../pages/hello-ionic/hello-ionic';
import { ListPage } from '../pages/list/list';
@Component({
templateUrl: 'app.html'
})
export class MyApp {
@ViewChild(Nav) nav: Nav;
// make HelloIonicPage the root (or first) page
rootPage: any = HelloIonicPage;
pages: Array<{title: string, component: any}>;
constructor(
public platform: Platform,
public menu: MenuController
) {
this.initializeApp();
// set our app's pages
this.pages = [
{ title: 'Hello Ionic', component: HelloIonicPage },
{ title: 'My First List', component: ListPage }
];
}
initializeApp() {
this.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();
});
}
openPage(page) {
// close the menu when clicking a link from the menu
this.menu.close();
// navigate to the new page if it is not the current page
this.nav.setRoot(page.component);
}
}
1.imports
import { Component, ViewChild } from '@angular/core';
import { Platform, MenuController, Nav } from 'ionic-angular';
import { StatusBar } from 'ionic-native';
import { HelloIonicPage } from '../pages/hello-ionic/hello-ionic';
import { ListPage } from '../pages/list/list';
最開始,有一些imports定義。我們用于加載其他組件或服務到這個組件。所有的組件,除了根組件,你都可以看到類定義是這樣的:
export class Something {
}
非常簡單,我們 expoet 組件就是為了能夠在其他地方 import。在這個例子里面,我們從 Ionic 庫導入了 Platform, Nav和 MenuController 服務。 Platform 提供了關于運行應用程序平臺的信息, Nav 提供應用里面導航的引用, MenuController 允許我們提供控制菜單。
我們從Angular 2導入 Component 和 ViewChild 。 Component 幾乎無處不在,因為我們用于創建組件, ViewChild 用于獲取組件中元素的定義。
我們也導入 importing 了 我們自己創建的HelloIonicPage 和 ListPage 組件。 這些組件定義在 src/pages/hello-ionic/hello-ionic.ts 和 src/pages/list/list.ts (根據 import 語句對應的路徑)。注意我們沒有包含src路徑在import中,因為是當前文件的相對路徑,而我們已經在src目錄中。因為我們在名為app的子文件夾中,所以我們到上級目錄使用../。
接下來我們看到從ionic-native導入 StatusBar,因為我們通過Ionic2使用Cordova來訪問本地功能,就像控制 status bar。Ionic Native是由Ionic提供的服務以便于方便使用Cordova插件。盡管你不用為了使用Ionic Native而包含Native functionatilty,你可以直接使用Cordova插件。
2. Decorator
Decorators,就像 @Component 和 @Directive,通過使用在類定義上添加元數據(擴充信息)給我們的組件,看看我買的 root component:
@Component({
templateUrl: 'app.html'
})
這里我們使用 templateUrl 讓組件知道使用哪個文件作為視圖 (你也可以使用 template 作為內聯模版而不是 templateUrl)。
3. Class 定義
之前的所有都沒有真正的做一些功能,只是一個設置和搭建。現在我們要開始定義一些行為,來看一看吧:
export class MyApp {
@ViewChild(Nav) nav: Nav;
// make HelloIonicPage the root (or first) page
rootPage: any = HelloIonicPage;
pages: Array<{title: string, component: any}>;
constructor(public platform: Platform, public menu: MenuController) {
this.initializeApp();
// set our app's pages
this.pages = [
{ title: 'Hello Ionic', component: HelloIonicPage },
{ title: 'My First List', component: ListPage }
];
}
initializeApp() {
this.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();
});
}
openPage(page) {
// close the menu when clicking a link from the menu
this.menu.close();
// navigate to the new page if it is not the current page
this.nav.setRoot(page.component);
}
}
首先我們定義一個新類MyApp,classes是ES6的新特性。
我們傳入一些參數到構造函數constructor:platform 和menu 然后它們的類型是 Platform 和MenuController。這樣我們通過構造函數注入inject了這些服務(比如MenuController 將作為菜單),通過使用public關鍵字使得作用域在整個類;意味著我們可以通過this.menu 或者 this.platform在這個類里面的任何地方訪問它們。
The Platform service提供了程序所運行平臺的相關信息 (例如:寬高、橫豎、分辨率等),這里我們用來判斷app是否就緒。
MenuController服務允許我們創建和管理一個滑動菜單。
在構造函數的上方,我們也定義了幾個成員變量用于保存我們類里的rootPage 和 pages。通過在構造函數上面定義,我們就可以在整個類里通過this.rootPage或 this.pages來使用。
我們定義 rootPage 為 HelloIonicPage 組件,作為首先顯示的第一頁(你也可以簡單的改變它,用ListPage代替)。
構造函數之外,我們定義了一個名為 openPage 的方法,傳入一個page參數,通過調用setRoot方法設置為當前頁。注意,我們獲取this.nav引用通過一種奇怪的方式。通常,我們導入NavController 使用與 MenuController 和Platform 同樣的方式然后調用它的 setRoot,但是你不能從根組件調用它,作為替換我們獲取引用通過Angular2提供的@ViewChild。
一個初學者特別困惑的事是這樣:
rootPage: any = HelloIonicPage;
pages: Array<{title: string, component: any}>;
之前提到過,這里是創建成員變量。但是你可能會想Array<{title: string, component: any}>是什么鬼。你應該知道,Ionic 2使用TypeScript,這些鬼就是types(類型)。類型簡單的說就是“這些變量應該只含有這些類型的數據”。這里,我們可以說rootPage可以包含any類型的數據,pages僅可以包含數組,而這些數組僅可以包含由字符串標題和any類型component組成的對象。這是一個非常復雜的類型,你可像下面這樣簡單處理:
rootPage: any = HelloIonicPage;
pages: any;
或者你也可以完全不用類型。使用類型的好處是給你的應用程序增加了錯誤檢查和一個基礎水平的測試——如果你的pages數組被傳入了一個數字,那么你的應用將被中斷,而這將直觀的去了解和處理。
Root Components 模版
當我們創建根組件是我們提供了一個模版給組件,就是被渲染到屏幕的內容。1).這里是我們在瀏覽器運行時根組件的樣子:
現在我們稍微詳細的看看模版HTML。
<ion-menu [content]="content">
<ion-header>
<ion-toolbar>
<ion-title>Pages</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<button ion-item *ngFor="let p of pages" (click)="openPage(p)">
{{p.title}}
</button>
</ion-list>
</ion-content>
</ion-menu>
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
先看下第一行:
<ion-menu [content]="content">
這是menu元素的content 的屬性為 content。記住這里的 “content” 是表達式而不是字符串。我們不是設置 content 屬性為字符串“content”,我們設置的是變量 “content”。如果你跳到文件底部你就會看到:
<ion-nav id="nav" [root]="rootPage" #content swipe-back-enabled="false"></ion-nav>
上面代碼通過添加#content,我們就創建了一個名為content的變量指向這個組件,也是menu的content屬性使用的變量。所以,menu將使用<ion-nav>作為它的主要內容。這里我們設置root屬性為我們在類中定義(app.ts)的rootPage。
接下來我們看看是什么有意思的東西:
<button ion-item *ngFor="let p of pages" (click)="openPage(p)">
{{p.title}}
</button>
在這一小塊代碼中擠進了Angular 2的語法。為構造函數中定義的每一個頁面創建一個按鈕,號語法意味這它將為每個頁面創建一個嵌入式模版(它不會在DOM中渲染出上面的代碼,而是使用模版創建),通過使用let p我們可以獲取到某個特定頁面的引用,用于點擊事件時傳遞到openPage方法(在根模塊中定義的)。回過頭去看看openPage方法可以看到這個參數用于設置rootPage*:
this.nav.setRoot(page.component);
App Module
我們已經覆蓋了一些根模塊的細節,但是這里還有一個名為app.modules.ts的神秘文件在app目錄下。
為了在我們的程序中使用頁面和服務,我們需要把它們添加到 app.module.ts文件。我們創建的所有頁面需要被添加到 declarations 和 entryComponents 數組,所有服務需要被添加到providers數組,所有自定義的組件或pipes只需要被添加到declarations數組。
你還會發現main.dev.ts 和 main.prod.ts 文件在同一個目錄下面。其中只有一個會被用到(取決于你是開發還是發布的build)。實際上它負責啟動您的應用程序(這個意義上它有點像index.html)。它將導入app module并啟動應用程序。
頁面
根組件是一個特例,我們通過 ListPage組件來看看如何添加一個普通的視圖到一個Ionic2應用程序。你能看到這個頁面,通過選擇應用程序中的“My First List”菜單,來查看這個頁面:
代碼是醬紫的:
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { ItemDetailsPage } from '../item-details/item-details';
@Component({
templateUrl: 'list.html'
})
export class ListPage {
selectedItem: any;
icons: string[];
items: Array<{title: string, note: string, icon: string}>;
constructor(public navCtrl: NavController, public navParams: NavParams) {
// If we navigated to this page, we will have an item available as a nav param
this.selectedItem = navParams.get('item');
this.icons = ['flask', 'wifi', 'beer', 'football', 'basketball', 'paper-plane',
'american-football', 'boat', 'bluetooth', 'build'];
this.items = [];
for(let i = 1; i < 11; i++) {
this.items.push({
title: 'Item ' + i,
note: 'This is item #' + i,
icon: this.icons[Math.floor(Math.random() * this.icons.length)]
});
}
}
itemTapped(event, item) {
this.navCtrl.push(ItemDetailsPage, {
item: item
});
}
}
和根組件一樣,我們有一些 import 語句,然后我們同樣有 @Component 修飾符:
@Component({
templateUrl: 'list.html'
})
然后是這樣的:
export class ListPage {
}
這里有個前綴 export 而在根組件中沒有。這允許我們的頁面組件在其他地方被導入(import)。
這個視圖中有個叫 NavParams 的組件通過構造函數加了進來。在導航的時候我們就可以返回這個視圖的詳細信息,我們先查一下值:
this.selectedItem = navParams.get('item');
這時是undefined,因為這個頁面被設置成了rootPage(在根組件中通過openPage方法設置),我們沒用通過navigation stack導航到這個頁面。
Ionic 2 中,如果你想添加一個視圖,并且保存頁面導航歷史隨時可以返回,那么你需要push這個頁面到n
navigation stack,對應的移除用pop。
在 ListPage 組件中,我們通過 itemTapped 方法(ListPage 模版中,但某條記錄被點擊時觸發) push 了 ItemDetailsPage :
itemTapped(event, item) {
this.navCtrl.push(ItemDetailsPage, {
item: item
});
}
上面push 了 ItemDetailsPage 組件到 navigation stack,使之成為當前活動視圖,然后把被點擊的item傳入詳情頁中。
現在我們看看細節頁面 ItemDetailsPage 組件的細節,我們可以使用 NavParams 獲取傳入記錄的細節,好像這樣(畢竟我們push了些東西到navigation stack):
this.selectedItem = navParams.get('item');
console.log(this.selectedItem);
這就是Ionic2主從復合的基本模式了。
還有就是記住,你可以通過命令行輕松創建頁面:
ionic g page MyPage
這將自動創建你需要的頁面文件。
總結
毫無疑問Ionic 2和Angular 2 取得了巨大的進步在組織結構和性能上,但他們看起來也很嚇人。盡管最初似乎需要很多學習和面對困擾,但我認為它很有意義。