New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Back button behavior for browser #6363

Closed
carsonip opened this Issue Apr 28, 2016 · 26 comments

Comments

Projects
None yet
@carsonip
Copy link

carsonip commented Apr 28, 2016

Type: feat

Ionic Version: 2.x

Platform: desktop browser

In #5071, the back button behavior for platforms are fixed. However, it does not fix the behavior for platform browser or mobile web.

Expected behavior: browser back button works the same as hardware back buttons in Ionic 2 apps.

There are quite a number of Ionic developers using Ionic for mobile web application. This Ionic 1 feature should exist in Ionic 2 despite the difference in navigation mechanism.

@samuba

This comment has been minimized.

Copy link

samuba commented Jul 5, 2016

As Ionic had a big announcment that it will be supporting Progressive Web Apps, I really wonder why this issue is not on the Roadmap. Or in any other discussion.
In my opinion this is nearly a showstopper for PWA with Ionic. Because navigation elements that the user is used to (browser back-button & hardware back-button) will actually "close" the application.
Please leave a statement for the plans on this.

@stepanh

This comment has been minimized.

Copy link

stepanh commented Jul 26, 2016

I agree with @samuba - please let us know your plans! Should we work on a workaround or wait for official solution?

I understand that Browser history won't be able to support all features of NavController. But, that's okay as the main thing is to prevent navigation away from the the App on back button - which is very confusing for the user.

And, having deep linking would be nice too.

@jgw96

This comment has been minimized.

Copy link
Contributor

jgw96 commented Jul 26, 2016

Hello! This is currently on our roadmap and actively being worked on. Thanks!

@jgw96 jgw96 closed this Sep 2, 2016

@jgw96 jgw96 reopened this Sep 2, 2016

@klemensz

This comment has been minimized.

Copy link

klemensz commented Oct 12, 2016

Hi,
is there already a solution for this? Currently with Ionic v2 RC0 when I open the app in the mobile browser (Chrome on Android), navigate to a sub-page and tap the "hardware" back button, I exit the app, which is not reasonable for users.

Or is there at least a workaround?

@TiagoSilvaPereira

This comment has been minimized.

Copy link

TiagoSilvaPereira commented Oct 13, 2016

+1 - Is there a solution for this? Thanks

@tarunagrawal

This comment has been minimized.

Copy link

tarunagrawal commented Oct 27, 2016

+1 - any solution yet ? Can team suggest any work around ?

@BAWES

This comment has been minimized.

Copy link

BAWES commented Nov 4, 2016

Anyone find a solution for this?

@almothafar

This comment has been minimized.

Copy link

almothafar commented Nov 12, 2016

@patrickmcd seriously why you thumb down people ! no comment too !

@tarunagrawal

This comment has been minimized.

Copy link

tarunagrawal commented Nov 13, 2016

Is there anybody who has solved this problem ?

@t00ts

This comment has been minimized.

Copy link

t00ts commented Nov 17, 2016

I stumbled upon this issue this morning. Using HTML5 browser history API and some Ionic magic I managed to get it working with just a few lines of code. I haven't thoroughly tested this yet, but it does appear like it's working on Chrome and Safari. Enjoy!

https://gist.github.com/t00ts/3542ac4573ffbc73745641fa269326b8

@TiagoSilvaPereira

This comment has been minimized.

Copy link

TiagoSilvaPereira commented Nov 17, 2016

Thanks by this solution!!!

2016-11-17 10:15 GMT-02:00 Abel Elbaile notifications@github.com:

I stumbled upon this issue this morning. Using HTML5 browser history API
and some Ionic magic I managed to get it working with just a few lines of
code. I haven't thoroughly tested this yet, but it does appear like it's
working on Chrome and Safari. Enjoy!

https://gist.github.com/t00ts/3542ac4573ffbc73745641fa269326b8


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
#6363 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ALYYXTBkh5MuvbZ1VIJtti6pm7R0JTc4ks5q_EVwgaJpZM4ISGTk
.

Acesse as páginas da nossa empresa e conheça os aplicativos que vão te
ajudar a economizar, a estar bem informado e muito mais! Aguarde
novidades...

[image: Vida App Fanpage] https://goo.gl/o3SdRs
[image: Vidaapp Linkedin Company Page] https://goo.gl/LNmTy7

Acesse também o nosso site: Vida App http://vidaapp.com.br

@BAWES

This comment has been minimized.

Copy link

BAWES commented Nov 17, 2016

@t00ts Thanks for the solution. However I can't seem to make it work. this._app.getActiveNav().canGoBack() is always false even when I have a page loaded. How to make this work when I have a nav with tabs on the loaded page?

@t00ts

This comment has been minimized.

Copy link

t00ts commented Nov 17, 2016

@BAWES Could you try replacing that piece of code with this._app.getRootNav().canGoBack().

@BAWES

This comment has been minimized.

Copy link

BAWES commented Nov 17, 2016

@t00ts That doesn't work either for my applications inner pages.
Your code functions perfectly on the initial pages, however once I load a <nav> which has menus and load a Tabs page within it, it doesn't seem to be able to reach the navCtrl from there.

I was able to make it function by passing the navCtrl from the loaded page to the app component through the Events pub/sub Ionic2 component.

In my app.component.ts

private _innerNavCtrl;

private _setupBrowserBackButtonBehavior () {

    // If on web version (browser)
    if (window.location.protocol !== "file:") {

      // Listen to browser pages
      this._events.subscribe("navController:current", (navCtrlData) => {
        this._innerNavCtrl = navCtrlData[0];
      });

      // 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;
        }

        // Navigate back on main active nav if there's a page loaded
        if (this._app.getActiveNav().canGoBack()){ 
          this._app.getActiveNav().pop();
        };

        // Navigate back on subloaded nav if notified
        if(this._innerNavCtrl && this._innerNavCtrl.canGoBack()){
          this._innerNavCtrl.pop();
        }

      };

      // Fake browser history on each view enter
      this._app.viewDidEnter.subscribe((app) => {
        history.pushState (null, null, "");
      });

    }
  }

Then in my actual loaded page:

ionViewDidEnter() {
    this._events.publish("navController:current", this.navCtrl);
  }

Its messy but working for now until I figure out the nav scoping issue im facing.

@samvloeberghs

This comment has been minimized.

Copy link

samvloeberghs commented Nov 25, 2016

@almothafar because the reactions were made with 👍 to remove those annoying +1's ;)

@almothafar

This comment has been minimized.

Copy link

almothafar commented Nov 26, 2016

@samvloeberghs but thumb down made even without +1, and it's not only +1 but question too ! it's not StackOverflow ... !

@jgw96

This comment has been minimized.

Copy link
Contributor

jgw96 commented Dec 29, 2016

Hello all! This has been solved since beta.11 with our deeplinker. Our deeplinker gives you routing which allows the browser back button to work just like the normal back button on your device. Thanks for using Ionic!

@jgw96 jgw96 closed this Dec 29, 2016

@judsonmusic

This comment has been minimized.

Copy link

judsonmusic commented Apr 19, 2017

@jgw96 The deeplinker link appears to be broken? Can you verify a solution for this?

@judsonmusic

This comment has been minimized.

Copy link

judsonmusic commented Apr 19, 2017

I was able to use this to accomplish what I need for now.

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, "");
      }

    });

  }
}
@jlbeard84

This comment has been minimized.

Copy link

jlbeard84 commented Apr 25, 2017

@judsonmusic Thanks for posting this. I was able to use this as a foundation and add in a nav controller pop to still maintain the integrity of ionViewCanLeave() implementations on pages and state when possible.

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;
        }

        if(
            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) => {
            if(this.routeHistory.length > 1) {
                history.pushState(null, null, "");
            }
        });
    }
}
@ddahan

This comment has been minimized.

Copy link

ddahan commented Jul 3, 2017

Thanks for the posted solutions, but at the first sight, it seems kind of hacky to me.
Is there some official way to do it recommended by Ionic @jgw96 ?
No offense but the "everything is solved by the deeplinker" (plus the link you provide is now dead) is not very helpful. Is there any official example to follow?
Thanks.

@alexmgrant

This comment has been minimized.

Copy link

alexmgrant commented Jul 4, 2017

@ddahan looks like they're working on updates to navigation this past release and next. Most likely why documentation disappeared and why they went dark.

https://github.com/ionic-team/ionic/blob/master/CHANGELOG.md#350-2017-06-28

@dhasilva

This comment has been minimized.

Copy link

dhasilva commented Jul 14, 2017

Odd, the link is not present for version 3.x, but it is present for version 2.x:

https://ionicframework.com/docs/2.3.0/api/navigation/DeepLinker/

@MartinMuzatko

This comment has been minimized.

Copy link

MartinMuzatko commented Jul 21, 2017

Page is no longer available. I found some docs about deeplinks here: http://ionicframework.com/docs/native/deeplinks/

@bogomips

This comment has been minimized.

Copy link

bogomips commented Nov 26, 2017

I tried the code in this repository https://gist.github.com/t00ts/3542ac4573ffbc73745641fa269326b8
While it actually closes the modals, it makes a mess when I navigate inside tabs, I get a sort of loop.
I ended removing

this._app.viewDidEnter.subscribe((app) => {    
history.pushState (null, null, "");
});

so now the modal closes but the whole page goes back which is still better than the original behaviour where the underneath page goes back and modal remains open!

This is a very misleading behaviour for the user. Probably for now, the only thing to do is to totally avoid modals and keep pushing pages, it would means to rewrite a large part of my PWA...

Is there any official solution, news, work arounds?

@ionitron-bot

This comment has been minimized.

Copy link

ionitron-bot bot commented Sep 1, 2018

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.

@ionitron-bot ionitron-bot bot locked and limited conversation to collaborators Sep 1, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.