How can I handle the back button action on Ionic 2?
I want to be able to know what to do depending on which page is being shown to the user.
I didn't find a good answer to this question, but after a while I figured it out myself a way to do it. I'm gonna share with you all.
Thanks
Here's how I did it:
In every Page component, I created a function called backButtonAction(), which will execute custom code for every page.
Code:
import { Component } from '#angular/core';
import { Platform, NavController, ModalController } from 'ionic-angular';
import { DetailsModal } from './details';
#Component({
selector: 'page-appointments',
templateUrl: 'appointments.html'
})
export class AppointmentsPage {
modal: any;
constructor(private modalCtrl: ModalController, public navCtrl: NavController, public platform: Platform) {
// initialize your page here
}
backButtonAction(){
/* checks if modal is open */
if(this.modal && this.modal.index === 0) {
/* closes modal */
this.modal.dismiss();
} else {
/* exits the app, since this is the main/first tab */
this.platform.exitApp();
// this.navCtrl.setRoot(AnotherPage); <-- if you wanted to go to another page
}
}
openDetails(appointment){
this.modal = this.modalCtrl.create(DetailsModal, {appointment: appointment});
this.modal.present();
}
}
And in the app.component.ts, I used the platform.registerBackButtonAction method to register a callback that will be called everytime the back button is clicked. Inside it I check if the function backButtonAction exists in the current page and call it, if it doesn't exists, just go to the main/first tab.
One could simplify this if they didn't need to perform customized actions for every page. You could just pop or exit the app.
I did it this way because I needed to check if the modal was open on this particular page.
Code:
platform.registerBackButtonAction(() => {
let nav = app.getActiveNav();
let activeView: ViewController = nav.getActive();
if(activeView != null){
if(nav.canGoBack()) {
nav.pop();
}else if (typeof activeView.instance.backButtonAction === 'function')
activeView.instance.backButtonAction();
else nav.parent.select(0); // goes to the first tab
}
});
if the current page is the first tab, the app closes (as defined in the backButtonAction method).
Ionic Latest version 3.xx app.component.ts file import { Platform, Nav, Config, ToastController} from 'ionic-angular';
constructor(public toastCtrl: ToastController,public platform: Platform) {
platform.ready().then(() => {
//back button handle
//Registration of push in Android and Windows Phone
var lastTimeBackPress=0;
var timePeriodToExit=2000;
platform.registerBackButtonAction(() => {
// get current active page
let view = this.nav.getActive();
if(view.component.name=="TabsPage"){
//Double check to exit app
if(new Date().getTime() - lastTimeBackPress < timePeriodToExit){
this.platform.exitApp(); //Exit from app
}else{
let toast = this.toastCtrl.create({
message: 'Press back again to exit App?',
duration: 3000,
position: 'bottom'
});
toast.present();
lastTimeBackPress=new Date().getTime();
}
}else{
// go to previous page
this.nav.pop({});
}
});
});
}
I used answers from here and other sources to accomplish what I needed.
I noticed that when you build the application for production (--prod) this approach doesn't work, because of JS uglifying and simplifying:
this.nav.getActive().name == 'PageOne'
Because of that, I use next in the "if" statement:
view.instance instanceof PageOne
So the final code looks like this:
this.platform.ready().then(() => {
//Back button handling
var lastTimeBackPress = 0;
var timePeriodToExit = 2000;
this.platform.registerBackButtonAction(() => {
// get current active page
let view = this.nav.getActive();
if (view.instance instanceof PageOne) {
if (new Date().getTime() - lastTimeBackPress < timePeriodToExit) {
this.platform.exitApp(); //Exit from app
} else {
let toast = this.toastCtrl.create({
message: 'Tap Back again to close the application.',
duration: 2000,
position: 'bottom',
});
toast.present();
lastTimeBackPress = new Date().getTime();
}
} else if (view.instance instanceof PageTwo || view.instance instanceof PageThree) {
this.openPage(this.pages[0]);
} else {
this.nav.pop({}); // go to previous page
}
});
});
As per Ionic 2 RC.4 documentation from here:
You can use registerBackButtonAction(callback, priority) method of Platform API to register the action on back button press.
The back button event is triggered when the user presses the native platform’s back button, also referred to as the “hardware” back button. This event is only used within Cordova apps running on Android and Windows platforms. This event is not fired on iOS since iOS doesn’t come with a hardware back button in the same sense an Android or Windows device does.
Registering a hardware back button action and setting a priority allows apps to control which action should be called when the hardware back button is pressed. This method decides which of the registered back button actions has the highest priority and should be called.
Parameters :
callback : Function to be called when the back button is pressed, if this registered action has the highest priority.
priority : Number to set the priority for this action. Only the highest priority will execute. Defaults to 0
Returns: Function : A function that, when called, will un-register the back button action.
Actually ionViewWillLeave works better in my case.
Here are the official docs about navigating lifecycle
I was able to accomplish this in the event that we are simply setting root pages...
import {Component, ViewChild, Injector} from '#angular/core';
import {Platform, MenuController, Nav, App, IonicApp, NavController} from 'ionic-angular';
import {StatusBar} from '#ionic-native/status-bar';
import {SplashScreen} from '#ionic-native/splash-screen';
import {InvitesPage} from "../pages/invites/invites";
import {RewardsPage} from "../pages/rewards/rewards";
import {ConnectionsPage} from "../pages/connections/connections";
import {MessagesPage} from "../pages/messages/messages";
import {ResourcesPage} from "../pages/resources/resources";
import {SignoutPage} from "../pages/signout/signout";
import {DashboardPage} from "../pages/dashboard/dashboard";
import {AccountPage} from "../pages/account/account";
import {HomePage} from "../pages/home/home";
import {TriviaPage} from "../pages/trivia/trivia";
import {Events} from "ionic-angular/util/events";
#Component({
templateUrl: 'app.html'
})
export class MyApp {
#ViewChild(Nav) nav: NavController;
// make HelloIonicPage the root (or first) page
public rootPage: any; //if logged in, go to dashboard.
public pages: Array<{title: string, component: any}>;
public user: any;
public routeHistory: Array<any>;
constructor(public platform: Platform,
public menu: MenuController,
public statusBar: StatusBar,
public splashScreen: SplashScreen,
private _app: App,
private _ionicApp: IonicApp,
private _menu: MenuController,
protected injector: Injector,
public _events: Events) {
this.initializeApp();
// set our app's pages
this.pages = [
{title: 'My Account', component: AccountPage},
{title: 'Dashboard', component: DashboardPage},
{title: 'Invites', component: InvitesPage},
{title: 'Rewards', component: RewardsPage},
{title: 'Connections', component: ConnectionsPage},
{title: 'Messages', component: MessagesPage},
{title: 'Resources', component: ResourcesPage},
{title: 'Trivia', component: TriviaPage},
{title: 'Sign Out', component: SignoutPage}
];
this.routeHistory = [];
this.user = {firstName: ''};
}
initializeApp() {
this.platform.ready().then(() => {
this._setupBrowserBackButtonBehavior();
let self = this;
if (sessionStorage.getItem('user')) {
this.user = JSON.parse(sessionStorage.getItem('user'));
self.rootPage = TriviaPage;
} else {
self.rootPage = HomePage;
}
this.routeHistory.push(self.rootPage);
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
this.statusBar.styleDefault();
this.splashScreen.hide();
});
}
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);
//store route history
this.routeHistory.push(page.component);
}
private _setupBrowserBackButtonBehavior() {
// Register browser back button action(s)
window.onpopstate = (evt) => {
// Close menu if open
if (this._menu.isOpen()) {
this._menu.close();
return;
}
// Close any active modals or overlays
let activePortal = 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();
this.nav.setRoot(this.routeHistory[this.routeHistory.length - 1]);
}
};
// Fake browser history on each view enter
this._app.viewDidEnter.subscribe((app) => {
if (this.routeHistory.length > 1) {
history.pushState(null, null, "");
}
});
}
}
I found the Easiest way, just add the following code in app.component:
this.platform.registerBackButtonAction((event) => {
let activePortal = this.ionicApp._loadingPortal.getActive() ||
this.ionicApp._modalPortal.getActive() ||
this.ionicApp._toastPortal.getActive() ||
this.ionicApp._overlayPortal.getActive();
if(activePortal && activePortal.index === 0) {
/* closes modal */
activePortal.dismiss();
} else {
if(this.nav.getActive().name == 'Homepage') { // your homepage
this.platform.exitApp();
}
else {
if(this.nav.canGoBack())
this.nav.pop();
this.nav.setRoot(Homepage);
}
}
},101);
That's it! No need to add extra code on every page!
Best practice solution after long search .
it works 100%, and tested it in real device
this.Platform.registerBackButtonAction(() => {
// try to dismiss any popup or modal
console.log("Back button action called");
let activePortal = this.ionicApp._loadingPortal.getActive() ||
this.ionicApp._modalPortal.getActive() ||
this.ionicApp._toastPortal.getActive() ||
this.ionicApp._overlayPortal.getActive();
if (activePortal) {
// ready = false;
activePortal.dismiss();
activePortal.onDidDismiss(() => { });
console.log("handled with portal");
return;
}
// try to close the menue
if(this.MenuController.isOpen()){
this.closeMenu();
return;
}
else if(this.nav.canGoBack()){
this.nav.pop();
return;
}else{
let activePage = this.nav.getActive().instance;
let whitelistPages = [LoginPage, HomePage];
// if current page is not in whitelistPage
// then back to home or login page first
if (whitelistPages.indexOf(activePage.constructor) < 0) {
this.nav.setRoot(this.userLoggedIn ? HomePage : LoginPage);
return;
}else if(whitelistPages.indexOf(activePage.constructor) > 0){
this.AppUtilities.showConfirm("Exit","Are you want to exist the app ? ").subscribe(
()=>{
this.Platform.exitApp();
},
()=>{}
)
}else{
console.error('cannot handel back button')
}
}
});
I have a slight different approach compare with #amr abdulaziz. Im using the setTimeout to control back or exit. Hope this would give another option for implement the back button.
initBackButtonBehaviour() {
this.platform.registerBackButtonAction(() => {
console.log("Back button pressed");
if (this.readyToExit) {
this.platform.exitApp();
return;
}
let activePortal = this.ionicApp._loadingPortal.getActive() ||
this.ionicApp._modalPortal.getActive() ||
this.ionicApp._toastPortal.getActive() ||
this.ionicApp._overlayPortal.getActive();
if (activePortal) {
activePortal.dismiss();
activePortal.onDidDismiss(() => { });
return; // stop any further action after closing any pop up modal or overlay
}
if (this.menuCtrl.isOpen()) {
this.menuCtrl.close();
return; // stop any further action after menu closed
}
else if (this.nav.canGoBack()) {
this.nav.pop();
return; // stop any further action after navigation pop
}
else {
let activePage = this.nav.getActive().instance;
let whiteListPages = [HomePage];
// if current page is not in whitelistPage
// then back to home or login page first
if (whiteListPages.indexOf(activePage.constructor) < 0) {
this.nav.setRoot(HomePage);
return;
} else if (whiteListPages.indexOf(activePage.constructor) >= 0) {
this.utils.showToast('Press back button again to exit', 1500);
this.readyToExit = true;
setTimeout(() => {
this.readyToExit = false;
}, 1500);
} else {
console.error('cannot handle back button');
}
}
}, 101);
I have Researched Many Things for back button handle Finally I Found a Good Solution for ionic latest Version 3.xx
//Check Hardware Back Button Double Tap to Exit And Close Modal On Hardware Back
let lastTimeBackPress = 0;
let timePeriodToExit = 2000;
this.platform.registerBackButtonAction(() => {
let activePortal = this.ionicApp._loadingPortal.getActive() || // Close If Any Loader Active
this.ionicApp._modalPortal.getActive() || // Close If Any Modal Active
this.ionicApp._overlayPortal.getActive(); // Close If Any Overlay Active
if (activePortal) {
activePortal.dismiss();
}
else if(this.nav.canGoBack()){
this.nav.pop();
}else{
//Double check to exit app
if (new Date().getTime() - lastTimeBackPress < timePeriodToExit) {
this.platform.exitApp(); //Exit from app
} else {
this.toast.create("Press back button again to exit");
lastTimeBackPress = new Date().getTime();
}
}
});
In Ionic 3 Lazy Loading, I never felt the need of Handling back behavior of browser where as for platform.is('cordova') I have created following method which handles all back scenarios:
// If a view controller is loaded. Just dismiss it.
let nav = this.app.getActiveNav();
let activePortal = this._ionicApp._loadingPortal.getActive() ||
this._ionicApp._modalPortal.getActive() ||
this._ionicApp._toastPortal.getActive() ||
this._ionicApp._overlayPortal.getActive();
if(activePortal && activePortal.index === 0) {
/* closes modal */
activePortal.dismiss();
return;
}
// If a state is pushed: Pop it.
if (this.nav.canGoBack()) {
this.nav.pop();
return;
} else
// Else If its a tabs page:
if (this.nav.getActiveChildNav()) {
const tabs: Tabs = this.nav.getActiveChildNav();
const currentTab = tabs.getActiveChildNavs()[0];
// If any page is pushed inside the current tab: Pop it
if(currentTab.canGoBack()) {
currentTab.pop();
return;
}
else
// If home tab is not selected then select it.
if(tabs.getIndex(currentTab) !=0){
tabs.select(0);
return;
}
}
else
// If a menu is open: close it.
if (this.menu.isOpen()) {
this.menu.close();
return;
}
if (this.exitApp) {
this.platform.exitApp();
return;
}
this.exitApp = true;
const toast = this.toastCtrl.create({
message: this.exitMessage || 'press again to exit',
duration: 4000,
position: 'bottom',
cssClass: 'exit-toastr',
});
toast.present();
setTimeout(() => {
this.exitApp = false;
}, 2000);
you can try this functions :
registerBackButton() {
this.platform.registerBackButtonAction(() => {
if (this.menu.isOpen()) {
console.log("Menu is open!", "loggedInMenu");
this.menu.close();
console.log("this.menu.isOpen(): " + JSON.stringify(this.menu.isOpen()));
return;
}
console.log("Checking for other pages");
let checkHomePage = true;
let max = Globals.navCtrls.length;
for (let i = 0; i < Globals.navCtrls.length; i++) {
let n = Globals.navCtrls[i];
if (n) {
if (n.canGoBack()) {
console.log("Breaking the loop i: " + JSON.stringify(i));
let navParams = n.getActive().getNavParams();
if (navParams) {
console.log("navParams exists");
let resolve = navParams.get("resolve");
if (resolve) {
n.pop().then(() => resolve({}));
} else {
n.pop();
}
} else {
n.pop();
}
checkHomePage = false;
return;
}
} else console.log("n was null!");
}
if (this.nav.getActive().instance instanceof TabsPage && !this.nav.canGoBack()) {
let popPageVal = this.backbuttonService.popPage();
console.log("popPageVal: " + JSON.stringify(popPageVal));
if (popPageVal >= 0) {
console.log("Switching the tab to: ", popPageVal);
this.switchTab(popPageVal);
} else {
console.log("Last page is HomePage");
if (this.alert) {
this.alert.dismiss();
this.alert = null;
} else {
this.showAlert();
}
}
} else {
console.log("Last page is not HomePage");
if (this.nav.canGoBack()) {
console.log("We can go back!");
this.nav.pop();
}
}
});
}
showAlert() {
this.alert = this.alertController.create({
title: "Exit?",
message: "Are you sure you want to exit?",
buttons: [
{
text: "Cancel",
role: "cancel",
handler: () => {
this.alert = null;
}
},
{
text: "Exit",
handler: () => {
this.platform.exitApp();
}
}
]
});
this.alert.present();
}
switchTab(tabIndex) {
if (Globals.tabs && tabIndex >= 0) {
console.log("Switch condition met");
Globals.tabIndex = tabIndex;
Globals.tabs.select(tabIndex);
Globals.tabs.selectedIndex = tabIndex;
}
}
I hope its works with you.
Related
I have implemented one application in which the page will redirect to a page showing “no network” when the user gone offline. I have successfully implemented that. App navigates to the below screen when there is no network.In the below scenario the logic fails,’
Application fails to redirect to the " no network page " when the user opens the application for the first time or after kill during offline. In this case application launches the default root page say LoginPage.
Please find the logic below.
NetworkCheckProvider.ts
import { Injectable } from '#angular/core';
import { Network } from '#ionic-native/network';
import { Events } from "ionic-angular";
export enum ConnectionStatusEnum {
Online,
Offline
}
#Injectable()
export class NetworkCheckProvider {
private previousStatus;
constructor(private eventCtrl: Events,
private netWork:Network) {
this.previousStatus = ConnectionStatusEnum.Online;
}
public initializeNetworkEvents(): void {
this.netWork.onDisconnect().subscribe(() => {
if (this.previousStatus === ConnectionStatusEnum.Online) {
this.eventCtrl.publish('network:offline');
}
this.previousStatus = ConnectionStatusEnum.Offline;
});
this.netWork.onConnect().subscribe(() => {
if (this.previousStatus === ConnectionStatusEnum.Offline) {
this.eventCtrl.publish('network:online');
}
this.previousStatus = ConnectionStatusEnum.Online;
});
}
}
app.component.ts
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
this.networkCheck();
this.netWorkCheck.initializeNetworkEvents();
}
networkCheck() {
this.events.subscribe('network:online', () => {
this.generic.showToast("Network Available");
console.log('network connected!');
});
this.events.subscribe('network:offline', () => {
debugger;
if(this.nav.getActive().name!='NoInternetPage' || this.nav.getActive().name==null)
this.nav.push('NoInternetPage');
});
}
}
Please correct me if there is any mistake in my code. Also is there is any way to disable all click events during offline?
Thanks and Regards
Anand Raj
I use inappbrowser plugin in ionic 2 application like this :
import {InAppBrowser} from 'ionic-native';
and use it like this :
launch(url){
this.browser = new InAppBrowser( url, "_blank", "EnableViewPortScale=yes,closebuttoncaption=Done" );
this.browser.on("exit")
.subscribe(
() => {
this.close_event=true;
},
err => {
console.log("InAppBrowser Loadstop Event Error: " + err);
});
}
and in html :
<button ion-button icon-right color="danger" (click)="launch('https://www.example.com')">launch
<ion-icon name="refresh"></ion-icon>
when click on launch button for first time and after close browser, exit event not fire but when for second time click on launch button and after close browser exit event is fire
Perhaps the first time you click on Launch button, the device platform is not ready yet?
Try to put the calls to any InAppBrowser methods inside Plaform.ready(), like so:
...
import { Platform } from 'ionic-angular';
import { InAppBrowser } from '#ionic-native/in-app-browser';
...
export class HomePage {
private iab: InAppBrowser;
private platform: Platform;
private browser;
...
var launch = function(url) {
this.platform.ready().then(
() => {
this.browser = this.iab.create( url, "_blank", "EnableViewPortScale=yes,closebuttoncaption=Done" );
this.browser.on("exit").subscribe(
(event) => {
this.close_event = true;
},
(err) => {
console.log("InAppBrowser Loadstop Event Error: " + err);
}
);
}
);
};
...
}
I am using FCM PLugin Ionic 2
Below my app component
constructor(public menu1: MenuController,public alertCtrl:AlertController,public platform: Platform,public authservice:Authservice) {
this.initializeApp();
}
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();
Splashscreen.hide();
//Push Notifications
if(typeof(FCMPlugin) !== "undefined")
{
FCMPlugin.getToken(function(t){
console.log("Use this token for sending device specific messages\nToken: " + t);
}, function(e){
console.log("Uh-Oh!\n"+e);
});
FCMPlugin.onNotification(function(d){
if(d.wasTapped){
console.log(d);
var nav:NavController = this.app.getComponent("nav");
this.nav = nav;
nav.setRoot(MycomplaintsPage);
} else {
console.log("Push Notification", d);
console.log("you have new notifications");
}
}, function(msg){
}, function(err){
console.log("Arf, no good mate... " + err);
});
}
}
It's work Perfectly.But Click on Notifications Page cannot redirect.
I am getting this error
Uncaught TypeError: Cannot read property 'getComponent' of undefined.
How to fix this issue.
Kindly Advice me,
Thanks
The this object changes if you use regular functions i.e it belongs to the function not the class so this.app is nor defined. Arrow functions ()=>{} doesn't define this and the outer this is used.
Use arrow functions for callbacks
FCMPlugin.onNotification((d)=>{
if(d.wasTapped){
console.log(d);
var nav:NavController = this.app.getComponent("nav");
this.nav = nav;
nav.setRoot(MycomplaintsPage);
//...
});
Or set self=this before the call and use self.
let self = this.//declare here
FCMPlugin.onNotification(function(d){
if(d.wasTapped){
console.log(d);
var nav:NavController = self.app.getComponent("nav");//use here
this.nav = nav;
nav.setRoot(MycomplaintsPage);
//...
});
I make one application with ionic 2. I am trying to get a confirmation alert before close the application.
How can I do it ?
export class MyApp{
constructor(public alert: AlertController,public platform: Platform){}
exit(){
let alert = this.alert.create({
title: 'Confirm',
message: 'Do you want to exit?',
buttons: [{
text: "exit?",
handler: () => { this.exitApp() }
}, {
text: "Cancel",
role: 'cancel'
}]
})
alert.present();
}
exitApp(){
this.platform.exitApp();
}
}
If you would like to enable back button exit, add event listener for it and call exit function.
You can use this.platform.registerBackButtonAction(this.exit) for it.
I could find by myself the right solution:
https://forum.ionicframework.com/t/show-a-confirmation-alert-before-app-close-ionic/63313
showedAlert: boolean;
constructor(..., public alertCtrl: AlertController) {
}
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();
Splashscreen.hide();
this.showedAlert = false;
// Confirm exit
this.platform.registerBackButtonAction(() => {
if (this.nav.length() == 1) {
if (!this.showedAlert) {
this.confirmExitApp();
} else {
this.showedAlert = false;
this.confirmAlert.dismiss();
}
}
this.nav.pop();
});
});
}
confirmExitApp() {
this.showedAlert = true;
this.confirmAlert = this.alertCtrl.create({
title: "Salir",
message: "¿ Esta seguro que desea salir de la aplicación ?",
buttons: [
{
text: 'Cancelar',
handler: () => {
this.showedAlert = false;
return;
}
},
{
text: 'Aceptar',
handler: () => {
this.platform.exitApp();
}
}
]
});
this.confirmAlert.present();
}
Ionic 2+ quick solution: In your app.component.ts try
ngOnInit() {
this.platform.registerBackButtonAction(() => {
if (this.nav.canGoBack()) {
this.nav.pop();
} else {
// Currently on root page
this.appClosePromt();
}
}, 1);
}
appClosePromt() {
let alert = this.alertCtrl.create({
title: '',
message: 'Do you want to exit?',
buttons: [
{
text: 'No',
role: 'cancel',
handler: () => {
// Dismiss
}
},
{
text: 'Exit',
handler: () => {
this.platform.exitApp();
}
}
]
});
alert.present();
}
I am trying to catch 404 errors in my ember app, and redirect to /not-found.
I have an errors action on my ApplicationController, and I have an RSVP.on('error') function too but the 404's aren't getting caught. I just get a 404 error thrown to my console from jQuery, but the error is not getting passed to the error handler.
Errors initializer:
import Ember from 'ember';
var initialize = function(container) {
var errorReporting = container.lookup("service:errorReporting");
Ember.RSVP.on('error', function(err) {
Ember.warn("Ember.RSVP error..... Logging error:");
console.log(err);
if (err.name && err.name === 'TransitionAborted') {
Ember.debug("TransitionAborted error. Doesn't look like we should be catching these.");
} else {
container.lookup('route:application').send('error', err);
}
});
window.onerror = function(err) { // window general errors.
Ember.warn("Uncaught error (tripped window.onerror)..... Logging error:");
console.log(err);
errorReporting.report(err);
};
};
export default {
name: 'errors',
initialize: initialize
};
The error action on my applicationRoute is huge (and I can post it), but it doesn't even seem to be getting called.
EDIT 1: Route Code
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
titleToken: function(model) {
return model.get('name');
},
model: function(params) {
return this.store.find('location', params.location_id);
}
});
EDIT 2: ApplicationRoute / Error handler
error: function(err, transition) {
if (!Ember.isNone(transition)) {
transition.abort();
}
let errorHolder = this._getErrorDataFrom(err);
let errorMessage = this._getErrorMessageFrom(errorHolder);
let isFourOhFour = (typeof(err.status) !== 'undefined' && err.status === 404) || errorHolder.reason === 'not_found';
if (isFourOhFour) {
return this.transitionTo('not-found');
}
let requireAuthentication = (errorHolder.reason === 'not_authenticated');
if (requireAuthentication) {
window.localStorage.setItem('toast-on-reload', errorHolder.message);
return this.session.invalidate();
}
let isValidationError = ( errorHolder.reason === "validation_error" ||
( !Ember.isNone(errorHolder.errors) && !Ember.isNone(errorHolder.message) ) );
if (isValidationError) {
this.toast.error(errorMessage);
return;
}
let verificationRequired = (errorHolder.reason === "verification");
if (verificationRequired) {
this.toast.error(errorMessage);
return this.transitionTo('verification');
}
let invalidRequest = (errorHolder.reason === 'unprocessable_entity');
if (invalidRequest) {
this.toast.error(errorMessage);
return;
}
this.errorReporting.report(errorHolder);
this.toast.error(errorMessage);
return this.transitionTo('error');
}
},
_getErrorDataFrom: function(obj) {
if (!Ember.isNone(obj.responseJSON)) {
return obj.responseJSON;
} else if ( !Ember.isNone(obj.success) || !Ember.isNone(obj.errors)) {
return obj;
} else if (!Ember.isNone(obj.jqXHR) && !Ember.isNone(obj.jqXHR.responseJSON)) {
return obj.jqXHR.responseJSON;
} else {
Ember.warn("No error handler available, using default ( {} ). Error:");
console.log(obj);
return {};
}
},
_getErrorMessageFrom: function(errorHolder) {
if ( typeof(errorHolder.errors) === 'object' && !Ember.isNone(errorHolder.errors.message) ) {
return errorHolder.errors.message;
} else if (!Ember.isNone(errorHolder.errors)) {
return errorHolder.errors;
} else if (!Ember.isNone(errorHolder.message)) {
return errorHolder.message;
} else {
return "Sorry, something went wrong.";
}
}
If you want to use the error event, then place its handler inside an actions hash in the application route.
Alternatively, consider the use of an error route. You can define this in pods/application/error, with templates, routes, and controllers just like any other route. See http://guides.emberjs.com/v1.10.0/routing/loading-and-error-substates/#toc_code-error-code-substates. The error code will be passed to that error route as its model.
Finally, in many cases it's most simple and reliable to catch the error from the find.
model: function(params, transition) {
return this.store.find('location', params.location_id) .
catch(err => this.send('ajaxError', err));
}
Then define the ajaxError action on your application route which does the same kinds of things you are doing in your error hook now. However, this will catch only ajax errors, not other sorts of errors that might occur during transitions, and be swallowed (or in your case reported by Ember.RSVP.on('error').