Ionic 與 Angular 比較 在建立 Ionic 專案 中,我們練習了如何建立 Ionic,接下來對於 會開發 Angular 程式的人來說,最關注的應該就是 Ionic 跟 Angular 有什麼不同?要再多學些什麼?
啟動流程 我們依照程式執行順序來比較一下差異:
index.html 從 Browser 或 WebView 第一個取得的就是 index.html
網頁,在裡面我們可以看到 Ionic 多載入了 cordova.js
,就像之前說過的,我們可以看成 Ionic = Angular + Cordova ,本篇我們比較的是 Ionic 的 Angular 部分跟標準 Angular 程式有什麼差異,不會涵蓋 Cordova。 另一個亮點就是載入了 manifest.json
以及一段被註解起來負責執行 service worker 的 JavaScript,專案目錄下也看到一個 service-worker.js 檔案,由此可見 Ionic 本身也考慮到 PWA (Progressive Web App) 的模式,Angular 目前要自己手動加入,但是未來的版本應該可以期待。
index.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // [Angular] index.html <!doctype html > <html lang ="en" > <head > <meta charset ="utf-8" > <title > FirstApp</title > <base href ="/" > <meta name ="viewport" content ="width=device-width, initial-scale=1" > <link rel ="icon" type ="image/x-icon" href ="favicon.ico" > </head > <body > <app-root > </app-root > </body > </html >
index.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 // [Ionic] index.html <!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 ="manifest.json" > <meta name ="theme-color" content ="#4e8ef7" > <script src ="cordova.js" > </script > <link href ="build/main.css" rel ="stylesheet" > </head > <body > <ion-app > </ion-app > <script src ="build/polyfills.js" > </script > <script src ="build/vendor.js" > </script > <script src ="build/main.js" > </script > </body > </html >
對 Angular 來說我們是透過瀏覽器對某個網址所對應的 Server 發出 get 來取得網頁。 對 Ionic App 來說它是靠 Cordova 幫我們去將某個網頁載入到 WebView 控制項內,所以我們透過設定檔-config.xml
來預先告知 Cordova 程式開啟時要先載入哪個網頁。
main.ts 在 Angular 專案中,我們可以透過設定檔-.angular-cli.json
看到會優先執行的 js 檔。 而在 Ionic 則是直接宣告在網頁 body
的最後一行 ,因 Ionic 所有檔案都已經在本地端(手機內)了,所以不需要考慮延遲載入的需求,也因此它會先將其他組件優先載入,確保執行時所有組件都找的到。 比較一下 main.ts
可以說是一樣的,因為他們都指定了 AppModule 為起始模組。
當頁面資料過大時,其實有可能也需要延遲載入 的機制來做緩衝,Ionic 3 也幫我們顧慮到了,它的實作方式非常簡單,簡單到你已經套用了延遲載入 卻沒有發覺。
main.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { enableProdMode } from '@angular/core' ;import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' ;import { AppModule } from './app/app.module' ;import { environment } from './environments/environment' ;import 'hammerjs' ;if (environment.production) { enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule);
main.ts 1 2 3 4 5 6 7 import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' ;import { AppModule } from './app.module' ;platformBrowserDynamic().bootstrapModule(AppModule);
app.module.ts 接著比較 app.module.ts
可以看到 Angular 在 起始模組 (AppModule ) 中透過 bootstrap
屬性指定了 AppComponent 為起始元件 。
app.module.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { BrowserModule } from '@angular/platform-browser' ;import { NgModule } from '@angular/core' ;import { AppRoutingModule } from './app-routing.module' ;import { AppComponent } from './app.component' ;@NgModule ({ declarations : [AppComponent], imports : [ BrowserModule, AppRoutingModule ], providers : [], bootstrap : [AppComponent] }) export class AppModule { }
而 Ionic 看起來似乎也透過 bootstrap
屬性指定了 IonicApp ,不過仔細看一下 IonicApp 是來自 ionic-angular
,查看一下它的原始碼,似乎 IonicApp 也不算是一個 Angular Component,而在 imports
屬性上發現 MyApp 這個 Component 不是直接被加入,而是多個 IonicModule.forRoot(MyApp)
將它註記成起始元件 ,我們可以 Ionic 的模組就此時開始介入。
開啟 src\app\app.component.ts
可以看到類別名稱就是 MyApp ,在 Angular 專案內預設會是叫做 AppComponent 。 因為目前並沒有深入研究 IonicModule.forRoot
背後運作機制,所以不確定將它解釋成註記 是否正確,目前只能確定 Ionic 起始內容是此方法所指定的 Component。
app.module.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import { NgModule, ErrorHandler } from '@angular/core' ;import { BrowserModule } from '@angular/platform-browser' ;import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular' ;import { MyApp } from './app.component' ;import { AboutPage } from '../pages/about/about' ;import { ContactPage } from '../pages/contact/contact' ;import { HomePage } from '../pages/home/home' ;import { TabsPage } from '../pages/tabs/tabs' ;import { StatusBar } from '@ionic-native/status-bar' ;import { SplashScreen } from '@ionic-native/splash-screen' ;@NgModule ({ declarations : [ MyApp, AboutPage, ContactPage, HomePage, TabsPage ], imports : [ BrowserModule, IonicModule.forRoot(MyApp) ], bootstrap : [IonicApp], entryComponents : [ MyApp, AboutPage, ContactPage, HomePage, TabsPage ], providers : [ StatusBar, SplashScreen, {provide : ErrorHandler, useClass : IonicErrorHandler} ] }) export class AppModule {}
AppComponent vs. MyApp 在 Angular 中透過 @Component 這個裝飾器為 AppComponent 加入了選擇器屬性-selector
,Angular 會自動將網頁內含有該選擇器的 tag 替換成 AppComponent 的樣板。
app.component.ts 1 2 3 4 5 6 7 8 9 10 11 import { Component } from '@angular/core' ;@Component ({ selector : 'app-root' , templateUrl : './app.component.html' , styleUrls : ['./app.component.css' ] }) export class AppComponent {}
切換到 Ionic 可以發現 MyApp 的 @Component 裝飾器竟然沒有設定 selector
屬性,由剛才的 AppModule 說道 IonicModule.forRoot(MyApp)
的這個方法,可以知道 Ionic 是透過直接明確指定類別的方式宣告起始元件 。
app.component.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { Component } from '@angular/core' ;import { Platform } from 'ionic-angular' ;import { StatusBar } from '@ionic-native/status-bar' ;import { SplashScreen } from '@ionic-native/splash-screen' ;import { TabsPage } from '../pages/tabs/tabs' ;@Component ({ templateUrl : 'app.html' }) export class MyApp { rootPage :any = TabsPage; constructor (platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen ) { platform.ready().then(() => { statusBar.styleDefault(); splashScreen.hide(); }); } }
眼尖的人其實可以發現 Ionic 預設在 AppModule 內比 Angular 專案多設定了 entryComponents
屬性。 查詢 Angular 官網的 Dynamic Component Loader 可以看到說明,但是說明感覺很籠統,目前看起來應該是說只要是透過 ViewContainerRef.createComponent()
來建立的元件都需要註冊到 entryComponents
。
從建構式內還可看到 splashScreen.hide();
,Ionic 也提供給我們可以設定程式啟動的等待畫面。statusBar.styleDefault();
則是讓我們設定手機最上方的狀態列,例如我們希望 App 佔滿整個螢幕時可以把狀態列隱藏起來。
Angular 路由 vs. Ionic 導覽 開啟 Angular 專案 src\app\app.component.html
,我們可以看到最後一行也是最重要的一行 <router-outlet></router-outlet>
,Angular 的 路由模組 (RouterModule ) 就是透過這個 路由插座 (router-outlet
) 來插入符合路由規則的元件。
app.component.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // [Angular] src\app\app.component.html <div style ="text-align:center" > <h1 > Welcome to Ionic vs. Angular! </h1 > <img width ="300" src ="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==" > </div > <h2 > Here are some links to help you start: </h2 > <ul > <li > <h2 > <a target ="_blank" href ="https://angular.io/tutorial" > Tour of Heroes</a > </h2 > </li > <li > <h2 > <a target ="_blank" href ="https://github.com/angular/angular-cli/wiki" > CLI Documentation</a > </h2 > </li > <li > <h2 > <a target ="_blank" href ="https://blog.angular.io/" > Angular blog</a > </h2 > </li > </ul > <router-outlet > </router-outlet >
再來看 Ionic 專案可以發現預設並沒有加入 路由模組 ,更沒有類似 app-routing.module.ts
可以設定路由規則的檔案,開啟 src\app\app.html
可以看到 Ionic 改用自己的導覽元件-ion-nav
,並將 root
屬性指定給 rootPage
變數。
app.html 1 2 // [Ionic] src\app\app.html <ion-nav [root ]="rootPage" > </ion-nav >
再回過頭看看 app.component.ts
,rootPage
變數被設定成 TabsPage Component。
NavController 從上面我們可以概略知道以往我們在 Angular 上很習慣的會利用路由模組來作頁面切換 (Angular 屬於 SPA 程式,這邊的頁面 不是只整個 HTML 版面,而是指主要操作區域),而在 Ionic 上 卻是透過 ion-nav
來處理。 那 ion-nav
是什麼?
ion-nav is the declarative component for a NavController.
在查詢官方文件 可以知道 NavController 是導航控制器組件的基底類別,目前 Nav 和 Tab 2個 Component 都繼承自它-NavControllerBase 。 查詢 NavControllerBase 程式結構,我們可以看到一些導覽方法,例如:push
、pop
、insert
、remove
。
nav-controller-base.d.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import { ComponentFactoryResolver, ComponentRef, ElementRef, ErrorHandler, EventEmitter, NgZone, Renderer, ViewContainerRef } from '@angular/core' ;... export declare class NavControllerBase extends Ion implements NavController { parent : any ; _app: App; config: Config; ... constructor (parent: any , _app: App, config: Config, plt: Platform, elementRef: ElementRef, _zone: NgZone, renderer: Renderer, _cfr: ComponentFactoryResolver, _gestureCtrl: GestureController, _trnsCtrl: TransitionController, _linker: DeepLinker, _domCtrl: DomController, _errHandler: ErrorHandler ); push(page: any , params?: any , opts?: NavOptions, done?: TransitionDoneFn): Promise <any >; insert(insertIndex: number , page : any , params?: any , opts?: NavOptions, done?: TransitionDoneFn): Promise <any >; insertPages(insertIndex: number , insertPages : any [], opts?: NavOptions, done?: TransitionDoneFn): Promise <any >; pop(opts?: NavOptions, done?: TransitionDoneFn): Promise <any >; popTo(indexOrViewCtrl: any , opts?: NavOptions, done?: TransitionDoneFn): Promise <any >; popToRoot(opts?: NavOptions, done?: TransitionDoneFn): Promise <any >; popAll(): Promise <any []>; remove(startIndex: number , removeCount?: number , opts?: NavOptions, done?: TransitionDoneFn): Promise <any >; removeView(viewController: ViewController, opts?: NavOptions, done?: TransitionDoneFn): Promise <any >; setRoot(pageOrViewCtrl: any , params?: any , opts?: NavOptions, done?: TransitionDoneFn): Promise <any >; setPages(viewControllers: any [], opts?: NavOptions, done?: TransitionDoneFn): Promise <any >; _queueTrns(ti: TransitionInstruction, done : TransitionDoneFn): Promise <boolean >; _success(result: NavResult, ti : TransitionInstruction): void ; _failed(rejectReason: any , ti : TransitionInstruction): void ; _fireError(rejectReason: any , ti : TransitionInstruction): void ; _nextTrns(): boolean ; _startTI(ti: TransitionInstruction): Promise <void >; _loadLazyLoading(ti: TransitionInstruction): Promise <void >; _getEnteringView(ti: TransitionInstruction, leavingView : ViewController): ViewController; _postViewInit(enteringView: ViewController, leavingView : ViewController, ti : TransitionInstruction): void ; ... }
其實這樣看起來 Nav (ion-nav
) 與 Tab (ion-tab
) 與 Angular 的 路由插座 (router-outlet
) 相比更加強大,因為它們本身就提供的頁面切換的方法,可以將它本身的 content 內容替換成我們指定的元件類別,當然這都是因為它們繼承了 NavControllerBase。
Page 與 Angular 一樣我們可以透過下列指令協助我們快速建立各種類型檔案:ionic generate [<type>] [<name>]
generate
可以縮寫成 g
。
Ionic CLI 所提供的類型有:component
, directive
, page
, pipe
, provider
, tabs
,其中 page
與 tabs
是 Angular 所沒有的,所以我們就先建立一個 page
試試,指令如下:ionic g page page1
這邊筆者覺得 Angular CLI 做得比較好,因為 Angular CLI 會標示出建立那些檔案、修改那些檔案,Ionic CLI 目前只有一行成功訊息,這對於剛接觸的人可能會比較不親合。
我們可以看到 Ionic CLI 會依類型建立資料夾,並將新建的類型檔案放置到對應的資料夾下。Angular CLI 則是統一放置在 src\app\
下面。
接著我們開啟 src\pages\page1\page1.ts
,我們可以看到熟悉的裝飾器-@Component
,也就是說 page
本質上還是 Angular Component。
page1.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { Component } from '@angular/core' ;import { IonicPage, NavController, NavParams } from 'ionic-angular' ;@IonicPage ()@Component ({ selector : 'page-page1' , templateUrl : 'page1.html' , }) export class Page1Page { constructor (public navCtrl: NavController, public navParams: NavParams ) { } ionViewDidLoad ( ) { console .log('ionViewDidLoad Page1Page' ); } }
比較一下可以發現 Ionic Page 多了一些東西:
NavController
參數:我們已經知道 Ionic 是透過導覽模式來切換頁面,所以 Ionic CLI 貼心透過依賴注入 (Dependency Injection ,簡稱DI )的幫我們將 NavController 加到頁面類別內。
我們可以看到 constructor 內的參數都多了存取修飾詞-public
,此為 TypeScript 的語法糖,我們可以想成它會自動在 class 內建立一個同名的全域變數,並將值指向該變數,我們在其他方法內就可以直接透過 this.navCtrl.xxx
來使用 NavController 。
ionViewDidLoad()
:就像我們透過 Angular CLI 來建立 Component 時它會幫我們增加 ngOnInit()
方法一樣,這些方法都牽涉到生命週期。 查詢官方網站 可以看到 NavController Lifecycle events。
@IonicPage()
裝飾器:開啟其他預先建立好的 page 可以發現既沒有 Module 檔案,class 內也沒加上 @IonicPage()
裝飾器。 反過來看,這些預設的 page 卻跟 Angular Component 一樣被加入到 AppModule 的 declarations
,也同時設定到 entryComponents
屬性。
Angular 的 Component 必須註冊到 NgModule 內才可以使用。
page1.module.ts:開啟 src\pages\page1\page1.module.ts
可以看到這是一個 Angular Module, 而 Page1Page 被註冊到這個 NgModule 內。
page1.module.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { NgModule } from '@angular/core' ;import { IonicPageModule } from 'ionic-angular' ;import { Page1Page } from './page1' ;@NgModule ({ declarations : [ Page1Page, ], imports : [ IonicPageModule.forChild(Page1Page) ], }) export class Page1PageModule {}
延遲載入 (Lazy loaded) 接下來我們在 Home 的樣板上面加入一個按鈕,並在其點擊事件內透過 NavController 導覽至 Page1,開啟並編輯 src\pages\home\home.html
。
home.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // [Ionic] src\pages\home\home.html <ion-header > <ion-navbar > <ion-title > Home</ion-title > </ion-navbar > </ion-header > <ion-content padding > ... <p > Take a look at the <code > src/pages/</code > directory to add or change tabs, update any existing page or create new pages. </p > <button ion-button (click )="navPage1()" > Page 1</button > </ion-content >
接著編輯 src\pages\home\home.ts
,要注意的是傳給 navCtrl.push
的參數是 'Page1Page'
字串 而不是 Page1Page
類別。
home.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { Component } from '@angular/core' ;import { NavController } from 'ionic-angular' ;@Component ({ selector : 'page-home' , templateUrl : 'home.html' }) export class HomePage { constructor (public navCtrl: NavController ) { } navPage1 ( ) { this .navCtrl.push('Page1Page' ); } }
透過 ionic serve
來啟動專案,從瀏覽器上看頁面導覽效果已經出現了。 做到這般看似正常,但是有點怪怪的,Page1Page 這個 Component 雖然註冊到了 Page1PageModule 這個 NgModule,但是 Page1PageModule 並沒有註冊到起始模組-AppModule 內,所以依照 Angular 的邏輯來推論,AppModule 並不認得 Page1Page ,所以在載入時應該會出現錯誤,怎麼 Ionic 上卻沒有問題? 原來這就是 Ionic 的延遲載入 機制,也就是說透過 Ionic CLI 來建立 page
時,它就會以延遲載入 的架構來建立相關檔案,延遲載入 的重點架構如下:
Component 類別必須加上 @IonicPage() 裝飾器。
在 Component 路徑必須要有一個同名的 <component name>.module.ts
的 NgModule,只要名稱或路徑位置不對,CLI 編譯就會出錯,例如我們將 NgModule 改名為 page2.module.ts
。
NgModule 內除了註冊 Component 外,還需再 imports
屬性內加入 IonicPageModule.forChild(<component>)
。
NavController 透過 字串 (string ) 來指定要導覽到該頁面,如果將字串改成 Component 的類別執行導覽時就會出現錯誤。
反過來說如果我們不要使用延遲載入 的話要如何調整,如同剛剛修改的 home.ts
。
home.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { Component } from '@angular/core' ;import { NavController } from 'ionic-angular' ;import { Page1Page } from '../page1/page1' ;@Component ({ selector : 'page-home' , templateUrl : 'home.html' }) export class HomePage { constructor (public navCtrl: NavController ) { } navPage1 ( ) { this .navCtrl.push(Page1Page); } }
當 Component 已經有對應的 ngModule (page1.module.ts) 時,我們可以直接將該 Module 註冊到啟動模組內,或是啟動模組的延伸模組,將 src\app\app.module.ts
修改如下,再重新執行專案,應該就會正常。
app.module.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { NgModule, ErrorHandler } from '@angular/core' ;... import { Page1PageModule } from '../pages/page1/page1.module' ;@NgModule ({ declarations : [ ... ], imports : [ BrowserModule, IonicModule.forRoot(MyApp), Page1PageModule ], bootstrap : [IonicApp], entryComponents : [ ... ], providers : [ ... ] }) export class AppModule {}
當 Component 沒有對應的 ngModule 時,那就要跟預設的 page 一樣,將 Component 註冊到啟動模組的 declarations
以及 entryComponents
屬性內,將 src\app\app.module.ts
修改如下,再重新執行專案,應該同樣可以正常運作。
app.module.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import { NgModule, ErrorHandler } from '@angular/core' ;... import { Page1Page } from '../pages/page1/page1' ;@NgModule ({ declarations : [ ... TabsPage, Page1Page ], imports : [ BrowserModule, IonicModule.forRoot(MyApp) ], bootstrap : [IonicApp], entryComponents : [ ... TabsPage, Page1Page ], providers : [ ... ] }) export class AppModule {}
其實如果仔細觀察,沒有延遲載入 時頁面切換會有滑動效果,反之則沒有,在設計上這一點可能也要考量一下,例如操作功能的切換頁面可能透過延遲載入比較適合,因為我們大部分會透過 tabs 或是 menu 選單來做切換,但是同一個功能內的頁面切換不要透過延遲載入比較適合,因為上下頁面切換的滑動效果會讓整個功能比較有整體感。
參考文件:Ionic and Lazy Loading Pt 1
後進先出 (LIFO:Last In First Out) 我們先修改 page1 樣板,加入 input 輸入方塊,一個導覽按鈕,點擊時會再次導覽回 page1,一個導覽返回鈕,接著在類別內加入一個 page_length 屬性並給予 NavController 目前頁面堆疊數量,最後再透過嵌入繫結 方式繫結到樣板上。
Ionic 的 Component 都已經預先載入了,所以可以直接使用。
page1.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // [Ionic] src\pages\page1\page1.html <ion-header > <ion-navbar > <ion-title > page1</ion-title > </ion-navbar > </ion-header > <ion-content padding > Page Length: <hr > <ion-item > <ion-label fixed > Value:</ion-label > <ion-input type ="text" value ="" > </ion-input > </ion-item > <button ion-button (click )="navCtrl.push('Page1Page')" > Page 1</button > <button ion-button (click )="navCtrl.pop()" > Back</button > </ion-content >
page1.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { Component } from '@angular/core' ;import { IonicPage, NavController, NavParams } from 'ionic-angular' ;@IonicPage ()@Component ({ selector : 'page-page1' , templateUrl : 'page1.html' , }) export class Page1Page { page_length = 0 ; constructor (public navCtrl: NavController, public navParams: NavParams ) { } ionViewDidLoad ( ) { console .log('ionViewDidLoad Page1Page' ); this .page_length = this .navCtrl.length(); } }
啟動專案並透過瀏覽器操作。 由上面的操作,我們可以看到我們每次呼叫 push
方法時,Ionic 會在產生一個全新的 page1 並且取代目前頁面,從 page_length
屬性也可以印證 NavController
紀錄的頁面確實增加了,當呼叫 pop
方法時,可以發現 Ionic 會移除目前頁面並顯示上一頁面內容,我們也可以看到之前頁面的內容都會被保留下來。 我們可以假想 NavController
就像一個大箱子,每次 push
時就會放入一本新書,每次 pop
時就會取出最上面的一本書,我們能看得的書永遠是最後一本,因為它會被放在最上層,下層的書都會被遮住,網頁上看到的 page 也是一樣是最後載入的 page,所以我們可以說這是一種後進先出 (Last In First Out )的堆疊模式,最後進去的會最先出來。 了解這種模式之後,可以發現它會衍生出一些問題:
因此在設計上可以參考一些網站導航 的設計原則,例如階層不要太多可能3~5層,不同功能盡量避免可以直接切換,讓導覽途徑是樹枝展狀的階層圖,而不是交錯複雜的網狀圖。
[**ionic-first-app_2017-09-19.zip**](/uploads/ionic-first-app_2017-09-19.zip)