使用Angular和ionic构建Hybrid App(三)—— 导航、生命周期和浏览器
写在前面:这篇文章的草稿在我的电脑里躺了半年。这段时间困惑非常多,这篇文章也是其中之一,就是这个系列接下来应该用哪种方式来帮助大家了解和使用ionic。很多东西文档上已经写的很清楚,大家看看文档不就行了吗?这个疑惑尽管现在还没有解开,所以还希望各位读者能留言,来谈谈大家想要看到什么。谢谢大家的支持!
ionic尽管使用了Angular作为实现的框架,它内部的导航方式却不同于Angular这种基于路由方式,而是视图栈。
什么是视图栈?其实从字面意思就能理解,无非就是我们把视图的导航以栈的形式缓存,而我们显示的永远是栈顶,在需要显示的时候压栈,在返回的时候出栈。
在ionic中,用 NavController 来管理整个应用的视图栈。它提供了两个最基本的方法来管理视图栈,push()和pop(),在我们需要显示一个页面的时候,通过调用push()方法来展示,这个方法在你需要的时候还能从上一个页面传递参数,而返回上个页面,也仅仅需要调用pop()。另外,还可以通过popToRoot()来返回到根部页面,insert()在堆栈间插入某个页面等等。
这时候可能会问,如果我需要做返回到上个页面后刷新之类的需求,应该怎么处理呢?这里就要介绍一下生命周期机制了。
生命周期,就是一个页面存在在整个业务流程中间的某些状态,比如创建视图、视图加载中、视图加载完成、视图准备销毁、视图销毁中、视图销毁完成等等,通过生命周期事件,我们可以在业务代码中控制我们需要的初始化、数据更新、数据销毁等等。例如:
import { Component } from '@angular/core';
@Component({
template: 'Hello World'
class HelloWorld {
ionViewDidLoad() {
console.log("I'm alive!");
ionViewWillLeave() {
console.log("Looks like I'm about to leave :(");
}
ionic在页面导航中提供了一系列的生命周期事件:
ionViewDidLoad: 页面加载完毕。在ionic中,每个页面在创建后只会加载一次,因此这个事件只会触发一次,通常会在这个事件中放置一些初始化代码。
ionViewWillEnter: 页面准备展示。在ionViewDidLoad后触发。每次页面进入栈顶就会触发。
ionViewDidEnter: 页面展示完成。在ionViewWillEnter后触发。每次页面进入栈顶就会触发。
ionViewWillLeave: 页面准备退出,不代表销毁。每次页面退出栈顶就会触发。
ionViewDidLeave: 页面退出完毕,不代表销毁。每次页面退出栈顶就会触发。
ionViewWillUnload:页面将会被销毁。页面销毁前会触发,赶紧缓存一下重要的数据,离开这该死的鬼地方。
通常,我们经常会需要用到返回到上个页面后刷新,就可以在页面中注册ionViewWillEnter事件来进行页面的刷新。
我们将ionic代码编译打包成native app之后,发现在Android平台上返回键能正常的按照我们想要的逻辑进行返回。这得益于ionic提供的Platform插件中可以注册监听返回键registerBackButtonAction()。这时候,我遇到了一个问题,就是我昨天说,我希望能 All in Web 。
据我所知,ionic在serve阶段(就是我们开发的时候会使用ionic serve命令在浏览器中进行开发)就会生成带有service worker的PWA工程。Platform插件是通过js-bridge来和Android层交互,获取返回键的事件,那我们如何在浏览器中让Android返回键也能很好工作呢?
在Android系统的浏览器中,点击返回键触发的是history.back()事件,而在这个时候,我们可以监听到popstate事件,那么配合ionic我们应该怎么去做返回呢?我直接上 代码 。
import { Component, ViewChild } from "@angular/core";
import { Nav, Platform, MenuController, IonicApp, App } from "ionic-angular";
import { SplashScreen } from "@ionic-native/splash-screen";
import { StatusBar } from "@ionic-native/status-bar";
import * as moment from "moment";
import { environment } from "../environments/environment";
import { IdleService, RedirectService, SessionService } from "../core";
import { HomePage, LoginPage, PageA, PageB } from "../pages";
@Component({
templateUrl: "app.html"
export class MyApp {
@ViewChild(Nav) nav: Nav;
private version = environment.deployDateTime;
public rootPage: any = LoginPage;
public pages: Array<{ title: string, component: any }>;
public routeHistory: Array<any>;
constructor(
public menu: MenuController,
public platform: Platform,
private ionicApp: IonicApp,
private idleService: IdleService,
private redirectService: RedirectService,
private sessionService: SessionService,
private splashScreen: SplashScreen,
private statusBar: StatusBar) {
this.routeHistory = [];
this.initializeApp();
// used for an example of ngFor and navigation
this.pages = [
{ title: "Home", component: HomePage },
{ title: "Page A", component: PageA },
{ title: "Page B", component: PageB }
this.setRootPage();
this.redirectService.redirectToLogin$.subscribe(() => { this.nav.setRoot(LoginPage); });
public setRootPage(): void {
const token: string = this.sessionService.accessToken();
const tokenExpiration: Date = this.sessionService.expires();
const currentUtcDate: Date = moment(new Date()).utc().toDate();
if (token == null || token.length === 0 || tokenExpiration == null || tokenExpiration < currentUtcDate) {
this.rootPage = LoginPage;
} else {
this.rootPage = HomePage;
this.idleService.startIdleWatch();
public initializeApp(): void {
this.platform.ready().then(() => {
this.setupBrowserBackButtonBehavior();
this.statusBar.styleDefault();
this.splashScreen.hide();
public openPage(page: any): void {
// reset the content nav to have just this page
// we wouldn't want the back button to show in this scenario
this.nav.setRoot(page.component);
private pushRouteHistory(page: any): void {
let component: any = page;
if(page.component) {
component = page.component;
this.routeHistory.length === 0 ||
this.routeHistory.length > 0 && this.routeHistory[this.routeHistory.length - 1] !== component) {
this.routeHistory.push(component);
private setupBrowserBackButtonBehavior(): void {
window.onpopstate = (event) => {
console.log("<- Back Button Pressed");
if(this.menu.isOpen()) {
this.menu.close();
return;
if(this.ionicApp) {
let activePortal: any =
this.ionicApp._loadingPortal.getActive() ||
this.ionicApp._modalPortal.getActive() ||
this.ionicApp._toastPortal.getActive() ||
this.ionicApp._overlayPortal.getActive();
if(activePortal) {
activePortal.dismiss();
return;
if(this.routeHistory.length > 1) {
this.routeHistory.pop();
if(this.nav.canGoBack()) {
this.nav.pop().catch((reason) => {
console.log("Unable to navigate back:" + reason);
} else {
this.nav.setRoot(this.routeHistory[this.routeHistory.length - 1]);
this.nav.viewWillEnter.subscribe((page) => {
this.pushRouteHistory(page);
this.nav.viewDidEnter.subscribe((app) => {