23

I want to be able to override the BACK button on the navigation bar, and the hardware button.

I want this override to be for one specific controller, but not for the rest of the controllers.

  • it must be cancelled when the user moves to another screen

(using ionic v1.0.0 uranium-unicorn)


My reason is I have a list of items. Clicking on the list opens a details page, with 3 tabs. Each tab shares the same controller.

However, pressing BACK on any of those tabs must go back to the main list. That is how it works on native devices, so that is how I would like it to work on my hybrid app.


Many solutions provided online seem to be for older beta versions, or for registration outside of controllers.

A common solution for working with the Android hardware button inside a controller is:

$ionicPlatform.registerBackButtonAction(function (event) {
  if($state.current.name=="home"){
    alert("button back");
  }
}, 100);

However this doesn't seem to work on the soft navigation bar button, and it works across all controllers, not just the one.

Richard Le Mesurier
  • 29,432
  • 22
  • 140
  • 255
  • related question without working solution: [Ionic: How to override back button function?](http://stackoverflow.com/questions/31585320/ionic-how-to-override-back-button-function) – Richard Le Mesurier Aug 26 '15 at 16:18
  • 1
    if you want to able to override the ion-nav-back-button means you have to override the module ionnavbackbutton with your code in your ionicbundle.js , and the hardware back button can be override as $ionicPlatform.registerBackButtonAction(function () { if ($state.current.name == "home"){ navigator.app.exitApp(); } else { navigator.app.backHistory(); } }, 100); – Anil kumar Aug 27 '15 at 05:49

7 Answers7

61

It is possible to override both buttons in your controller, without any changes the the HTML code.

To summarise:

  • Soft navigation bar button - override $rootScope.$ionicGoBack()
  • Hard Android button - use $ionicPlatform.registerBackButtonAction()

Detailed explanations below.


The solution for overriding the soft navigation bar BACK button comes from understanding what Ionic does when that button is pressed.

From the Ionic docs for ion-nav-back-button, we already know that:

the button is automatically set to $ionicGoBack() on click/tap.

Searching the source code in ionic.bundle.js reveals how this is declared:

$rootScope.$ionicGoBack = function(backCount) {
    $ionicHistory.goBack(backCount);
};

Overriding this in your own controller is simple. Make sure you pass $rootScope into the controller and just modify the above code. It is a good idea to grab a pointer to the original function so you can restore it if required, or call into it when finished with your custom processing.

// grab pointer to original function
var oldSoftBack = $rootScope.$ionicGoBack;

// override default behaviour
$rootScope.$ionicGoBack = function() {
    // do something interesting here

    // uncomment below line to call old function when finished
    // oldSoftBack();
};

The solution for overriding the Android hardware BACK button, for only one controller, comes from the return value of the registerBackButtonAction() function, which does the deregistration of the override.

Call that deregistration method in the $scope.$on('$destroy'... handler.

var doCustomBack= function() {
    // do something interesting here
};

// registerBackButtonAction() returns a function which can be used to deregister it
var deregisterHardBack= $ionicPlatform.registerBackButtonAction(
    doCustomBack, 101
);

$scope.$on('$destroy', function() {
    deregisterHardBack();
});

More details here:


A full solution would require the following:

  • override soft navigation bar BACK button
  • override Android hard BACK button
  • scope would be a single controller
  • default behaviour restored

The following code illustrates how this can be done:

// run this function when either hard or soft back button is pressed
var doCustomBack = function() {
    console.log("custom BACK");
};

// override soft back
// framework calls $rootScope.$ionicGoBack when soft back button is pressed
var oldSoftBack = $rootScope.$ionicGoBack;
$rootScope.$ionicGoBack = function() {
    doCustomBack();
};
var deregisterSoftBack = function() {
    $rootScope.$ionicGoBack = oldSoftBack;
};

// override hard back
// registerBackButtonAction() returns a function which can be used to deregister it
var deregisterHardBack = $ionicPlatform.registerBackButtonAction(
    doCustomBack, 101
);

// cancel custom back behaviour
$scope.$on('$destroy', function() {
    deregisterHardBack();
    deregisterSoftBack();
});

This issue has been discussed on the Ionic forums & issues pages:

Community
  • 1
  • 1
Richard Le Mesurier
  • 29,432
  • 22
  • 140
  • 255
  • 2
    This worked really well for us. We had nested ui-views that we wanted the back button to work with. We ended up taking this approach and used "window.history.back()" in place of $ionicGoBack() in the sub-view's parent controller. Although ionic wasn't keeping track of the sub-view history stack the browser was. – koga73 Jan 11 '16 at 18:19
  • @koga73 nice idea - I hadn't thought of doing it that way round. – Richard Le Mesurier Jan 11 '16 at 18:30
  • @RichardLeMesurier I am having a doubt that i have asked as question in stack overflow please take a look at it [http://stackoverflow.com/questions/36170488/how-to-override-the-default-back-button-in-ionic]. – Mohan Gopi Mar 23 '16 at 06:13
  • Great approach, but I can't call the original go back function after my custom go back code. Solution was to `oldSoftBack(-1)` inside my custon go back code. – Sebastian Jul 01 '16 at 15:32
  • @RichardLeMesurier - I have encountered a problem with the above. I have 5 main tabs...each Tab leads to Sub-Tabs. I am applying the above to the controller for TabA_SubTabA. My custom back is as follows: `var doCustomBack = function() { $state.transitionTo('tab.A'); }` - and it works. However, if while in SubTabA and I nagivate to another main tab (Tab C), and click on one of TabC-SubTabC pages, the back button on that SubTabC takes me straight back to TabA. The `$scope.$on('$destroy', function() { deregisterSoftBack(); })` properly restoring the original back functionality. – rolinger Nov 17 '16 at 21:09
  • I've ported this to Typescript for anyone using TS instead: https://gist.github.com/rinogo/1bd363c1b1ce19ef59477b55ea63b474 – rinogo Dec 21 '16 at 00:51
6

I took Richard's suggestion and put it into a service to make it more reusable.

The Controller

angular.module('MainApp').controller('MyController', ['backButtonOverride'], function (backButtonOverride) {
    // override back button for this controller
    backButtonOverride.setup($scope, function() {
        console.log("custom back");
    });
}

The Service

angular.module('MainApp.services', []).factory('backButtonOverride', function ($rootScope, $ionicPlatform) {
    var results = {};

    function _setup($scope, customBackFunction) {
        // override soft back
        // framework calls $rootScope.$ionicGoBack when soft back button is pressed
        var oldSoftBack = $rootScope.$ionicGoBack;
        $rootScope.$ionicGoBack = function() {
            customBackFunction();
        };
        var deregisterSoftBack = function() {
            $rootScope.$ionicGoBack = oldSoftBack;
        };

        // override hard back
        // registerBackButtonAction() returns a function which can be used to deregister it
        var deregisterHardBack = $ionicPlatform.registerBackButtonAction(
            customBackFunction, 101
        );

        // cancel custom back behaviour
        $scope.$on('$destroy', function() {
            deregisterHardBack();
            deregisterSoftBack();
        });
    }

    results.setup = _setup;
    return results;
});
oalbrecht
  • 413
  • 4
  • 11
  • This looks like a better way of doing things - I'm glad someone with actual JS knowledge could take my method a step further. – Richard Le Mesurier Apr 20 '16 at 06:21
  • 1
    Thanks. I'm no expert in JS, but this approach did seem to work for me. If anyone with more JS knowledge wants to refactor my code, feel free to! – oalbrecht Apr 20 '16 at 13:06
  • Thanks, after quite long time (couple of hours) of trial and error this approach seemed to work well :) – el.severo Sep 14 '16 at 11:16
  • @oalbrecht - I have encountered an issue with this backbutton override - the problem is that the `$scope.$on('$destroy'` doesn't seem to fully release the override. Once the custom override loads and I navigate to other tabs/pages and use the back button (soft and hard) it takes me back to the location defined in customBackFunction() - I am not using mine as a service, but the way Richard defined it above. More details here: [release override](http://stackoverflow.com/questions/40664271/how-to-restore-ionic-backbutton-after-override-breaking-backbutton-on-other-t) – rolinger Nov 18 '16 at 15:11
  • I've ported this to Typescript for anyone using TS instead: https://gist.github.com/rinogo/1bd363c1b1ce19ef59477b55ea63b474 – rinogo Dec 21 '16 at 00:51
2

are you talking about the soft navigation as in the back button on the ion-header-bar or ion-nav-bar? cause that is a easy fix. Just make your own custom header bar for that template. so on that state template just use something like this.

<div class="bar bar-header bar-positive">
        <button ng-click="someCustomFunction()" class="button button-clear button-light icon-left ion-chevron-left">Go Back</button>
</div>
Richard Le Mesurier
  • 29,432
  • 22
  • 140
  • 255
Jess Patton
  • 2,476
  • 1
  • 15
  • 26
  • if you look at the ionic css docs they will tell you how to build a simple html and css only header bar, then you can add any functionality you want to it within your controller rather than using the ion-nav-bar directive. – Jess Patton Aug 26 '15 at 19:20
  • yeah if you go to docs you will css on the left in the fast nav list. Then it is right at the top of the css docs. – Jess Patton Aug 26 '15 at 19:56
1

The above answer to override $rootScope.$ionicGoBack partially works.

The problem is with the way of deregisterSoftBack. I tried the above mentioned $scope.$on('$destroy', a_function) and also the new $scope.$on('$ionicView.beforeLeave', a_function), neither works.

The reason for it: the new controller will be entered before the deregisterSoftBack thus make the deregister fail. So I modified the solution a bit to make it work.

  1. change the

    var oldSoftBack = $rootScope.$ionicGoBack
    

    to

    $rootScope.oldSoftBack = $rootScope.$ionicGoBack
    
  2. deregister in $rootScope.$on("$stateChangeStart", your_function), the code is:

    if ($rootScope.oldSoftBack) {
        $rootScope.$ionicGoBack = $rootScope.oldSoftBack;
        $rootScope.oldSoftBack = null;
    }
    
raven.zuo
  • 111
  • 1
  • 9
  • ah...I think this might be my problem. When I navigate away to other Tabs and use the backbuttons there it takes me back to the customBackButton defined in the one controller that overrode the backbuttons to begin with. Will post an update. – rolinger Nov 18 '16 at 15:18
0

Took the feedback from @raven.zuo and made some amends to un-register the state change event.

(function () {
    'use strict';

    angular
        .module('appName')
        .service('customBackButtonService', customBackButtonService);

    customBackButtonService.$inject = ['$rootScope', '$ionicPlatform'];
    function customBackButtonService($rootScope, $ionicPlatform) {

        var service = {
            setup: setup
        };

        return service;

        ////////////////

        function setup(customBackFunction) {
            // override soft back
            // framework calls $rootScope.$ionicGoBack when soft back button is pressed
            $rootScope.oldSoftBack = $rootScope.$ionicGoBack;
            $rootScope.$ionicGoBack = function () {
                customBackFunction();
            };
            var deregisterSoftBack = function () {
                $rootScope.$ionicGoBack = $rootScope.oldSoftBack;
            };

            // override hard back
            // registerBackButtonAction() returns a function which can be used to deregister it
            var deregisterHardBack = $ionicPlatform.registerBackButtonAction(
                customBackFunction, 101
            );

            // cancel custom back behaviour
            var backStateChangeWatcher = $rootScope.$on('$stateChangeStart', function () {
                if($rootScope.oldSoftBack){
                    deregisterHardBack();
                    deregisterSoftBack();

                    // Un-register watcher
                    backStateChangeWatcher();
                }
            });
        }
    }
})();

//Called via:

    customBackButtonService.setup(function () {
        console.log('custom back');
    });
Slava.K
  • 3,073
  • 3
  • 17
  • 28
johnw86
  • 121
  • 1
  • 6
0

this is my solution :)

Put this part of code in your app.js run function :

//** Go Back interception function ------------------------------------------

    var currentScope;

    var defaultGoBack = $rootScope.$ionicGoBack;

    $rootScope.$ionicGoBack = function() {
        if ( angular.isFunction( currentScope.customGoBack ) ) {

            //assign default go back function to as a "super" function ^^
            currentScope.customGoBack.super = defaultGoBack;

            //if there is a custom back function, execute-it
            currentScope.customGoBack();

        } else {
            //else, execute default go back
            defaultGoBack();
        }
    };

    //Store targetScope to global each time the view is changing
    $rootScope.$on( '$ionicView.beforeEnter', function( event ) {
        currentScope = event.targetScope;
    });

Now, you are able to create a custom goback function in controllers :

$scope.customGoBack = function() {
   console.log( "customGoBack" );
   $scope.customGoBack.super();
};

This function will be automatically called when user tap on nav-back-button.

If you want to call goBack by yourself, you can do it by this way :

$rootScope.$ionicGoBack();

If you want to bypass the custom function whereas it is declared, here you go :

$ionicHistory.goBack();

You can assign different behavior for the back button directly in each controllers :)

mopi
  • 1
  • 3
0

For those using the latest versions of capacitor and ionic, this worked:

import { Capacitor } from "@capacitor/core";
import { App as CapacitorApp } from "@capacitor/app";

const App: React.FC = () => {
  const history = useHistory();
  useEffect(() => {
    if (Capacitor.isNativePlatform()) {
      CapacitorApp.addListener("backButton", () => {
        history.goBack();
      });
    }
  });

  return (
    <IonApp>...