Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS app breaks the back button when used for a single page application within another website

Note: I am using AngularJS v1.6.0

I have implemented a stand-alone AngularJS app into another existing website, it exists on a single page on the site and the rest of the site doesn't have any Angular code running on it.

For example, a regular page on the site could be at:

http://example.com/any-page

Then the user can click on a link and it takes them to the page with the Angular JS running on it:

http://example.com/angularjs-app

When landing on this URL it loads the AngularJS app and appends #!/ to the URL as expected. It doesn't contain any elements from the rest of the site such as the header, so for the user it looks like a completely different section. However it also breaks the back button. It's not possible press back to go back to http://example.com/any-page. Each time you press back, it just loads the landing view of the AngularJS app again - effectively the user is stuck on the AngularJS app page and cannot go back to /any-page.

I think this has something to do with AngularJS routing, because it seems to refresh the #!/ portion of the URL when you press back, and just reload the initial view on the app. But I could be wrong.

Note that the back button works fine within the AngularJS app when visiting various routes. For example, if I navigate between various routes/views in the app, such as #!/login or #!/view-details, I can always go back through these views via the back button. But when it reaches that initial view, it stops working.

Does anyone know a way around this?

Note: I have looked at various other Stack Overflow posts on this, however they all seem to be concered with the back button not working at all, rather than this issue where the back button works for navigating between routes within the app, but not back to the original non-AngularJS page on the site.

Routing config

(function () {
    "use strict";

    var routes = {
        error: "/error",
        forgottenPassword: "/forgotten-password",
        home: "/home",
        login: "/login",
        orders: "/orders",
        paymentDetails: "/payment-details",
        personalDetails: "/personal-details",
        subscriptions: "/subscriptions",
        updatePassword: "/update-password",
        accountVerification: "/account-verification",
        register: '/register',
        profile: '/profile',
        accountConfirm: '/account-confirm',
        deleteAccount: '/delete-account'
    };

    var configFunc = function (
        $routeProvider,
        $locationProvider,
        CONFIG,
        ROUTES) {

        var resolve = {
            isLoggedIn: [
                "$q", "ERRORS", "core.services.sessionsService", function ($q, ERRORS, sessionsService) {
                    return sessionsService.isLoggedIn().then(function (isLoggedIn) {
                        if (isLoggedIn) {
                            return isLoggedIn;
                        }

                        return $q.reject({ error: ERRORS.route.requiresAuth });
                    });
                }
            ]
        };

        var getTemplateUrl = function(page) {
            return CONFIG.rootPagesUrl + page;
        };

        $routeProvider
            .when("/", {
                controller: "StartCtrl",
                template: ""
            })
            .when(ROUTES.login, {
                templateUrl: getTemplateUrl("login.html"),
                pageName: "Login"
            })
            .when(ROUTES.forgottenPassword, {
                templateUrl: getTemplateUrl("forgotten-password.html"),
                pageName: "Forgotten Password"
            })
            .when(ROUTES.home, {
                templateUrl: getTemplateUrl("home.html"),
                resolve: resolve
            })
            .when(ROUTES.personalDetails, {
                templateUrl: getTemplateUrl("personal-details.html"),
                resolve: resolve
            })
            .when(ROUTES.paymentDetails, {
                templateUrl: getTemplateUrl("payment-details.html"),
                resolve: resolve
            })
            .when(ROUTES.orders, {
                templateUrl: getTemplateUrl("orders.html"),
                resolve: resolve
            })
            .when(ROUTES.subscriptions, {
                templateUrl: getTemplateUrl("subscriptions.html"),
                resolve: resolve
            })
            .when(ROUTES.updatePassword, {
                templateUrl: getTemplateUrl("update-password.html"),
                pageName: "Update Password"
            })
            .when(ROUTES.accountVerification, {
                templateUrl: getTemplateUrl("account-verification.html"),
                pageName: "Account Verification"
            })
            .when(ROUTES.error, {
                templateUrl: getTemplateUrl("error.html"),
                pageName: "Error"
            })
            .when(ROUTES.register, {
                templateUrl: getTemplateUrl("register.html"),
                pageName: "Register"
            })
            .when(ROUTES.profile, {
                templateUrl: getTemplateUrl("profile.html"),
                resolve: resolve,
                pageName: "Profile"
            })
            .when(ROUTES.accountConfirm, {
                templateUrl: getTemplateUrl("accountConfirm.html"),
                pageName: "Registration Complete"
            })
            .when(ROUTES.deleteAccount, {
                templateUrl: getTemplateUrl("deleteAccount.html"),
                resolve: resolve,
                pageName: "Delete Account"
            })
            .otherwise({
                templateUrl: getTemplateUrl("login.html"),
                pageName: "Login"
            });
    };

    var config = [
        "$routeProvider",
        "$locationProvider",
        "CONFIG",
        "ROUTES",
        configFunc
    ];

    var module = angular.module("app");
    module.constant("ROUTES", routes);
    module.config(config);
})();

Portion of index where ng-view lives:

<body ng-app="app" ng-strict-di>

    <div>
        <div id="container" class="mpp-app">
            <div class="mpp-page" id="mpp-page">
                <div class="page-wrapper">
                    <div class="ui-module-container">
                        <div brand></div>
                    </div>
                    <div ng-view></div>
                </div>
            </div>
             <div class="ui-module-container">
                 <div footer></div>
             </div>
         </div>
    </div>

    <div id="spinner" class="hidden"><div class="icon"></div></div>

StartCtrl

(function () {
    "use strict";

    var func = function (
        $rootScope,
        $scope,
        $location,
        ROUTES,
        APP_EVENTS,
        CORE_EVENTS,
        SessionModel,
        sessionsService,
        configurationAggregator) {

        var broadcast = function(event, args) {
            $rootScope.$broadcast(event, args);
        };

        var redirectToLogin = function() {
            broadcast(APP_EVENTS.navigation.login);
        };

        // check if user is signed in and has a valid session
        var verifySession = function() {
            sessionsService.verify().then(function() {
                // if user is logged in navigate to profile
                // otherwise navigate to login
                configurationAggregator.getConfiguration().then(function () {
                    broadcast(APP_EVENTS.auth.login.success);
                    //broad cast(APP_EVENTS.navigation.home);
                    broadcast(APP_EVENTS.navigation.uktvProfile);
                }, redirectToLogin);
            }, redirectToLogin);
        };

        // init
        var sessionId = $location.search().guid;

        if (angular.isDefined(sessionId) && sessionId !== null) {
            broadcast(CORE_EVENTS.session.changed, { sessionId: sessionId });
            verifySession();
        } else {
            verifySession();
        }
    };

    var controller = [
        "$rootScope",
        "$scope",
        "$location",
        "ROUTES",
        "EVENTS",
        "mpp.core.EVENTS",
        "mpp.core.SessionModel",
        "mpp.core.services.sessionsService",
        "mpp.core.aggregators.configurationAggregator",
        func
    ];

    var module = angular.module("app");
    module.controller("StartCtrl", controller);
})();
like image 372
shrewdbeans Avatar asked Oct 18 '22 08:10

shrewdbeans


1 Answers

It is not an issue of the AngularJS Router (although might be a bug of the specific version, so please add information about the version to the question). But most likely you have a redirect in a router event(s)' handler, or in StartCtrl.

The example from the AngularJS tutorial works fine, you can get it here:

git clone https://github.com/angular/angular-phonecat.git

To localize the issue i would first try to run your app with following config:

$routeProvider
        .when('/', {
            template: '<div>Hello</div>'
        })
        .when(ROUTES.login, {
            templateUrl: getTemplateUrl("login.html"),
            pageName: "Login"
        })
        .when(ROUTES.forgottenPassword, {
            templateUrl: getTemplateUrl("forgotten-password.html"),
            pageName: "Forgotten Password"
        })
        .when(ROUTES.home, {
            templateUrl: getTemplateUrl("home.html"),
            resolve: resolve
        })
        .when(ROUTES.personalDetails, {
            templateUrl: getTemplateUrl("personal-details.html"),
            resolve: resolve
        })
        .when(ROUTES.paymentDetails, {
            templateUrl: getTemplateUrl("payment-details.html"),
            resolve: resolve
        })
        .when(ROUTES.orders, {
            templateUrl: getTemplateUrl("orders.html"),
            resolve: resolve
        })
        .when(ROUTES.subscriptions, {
            templateUrl: getTemplateUrl("subscriptions.html"),
            resolve: resolve
        })
        .when(ROUTES.updatePassword, {
            templateUrl: getTemplateUrl("update-password.html"),
            pageName: "Update Password"
        })
        .when(ROUTES.accountVerification, {
            templateUrl: getTemplateUrl("account-verification.html"),
            pageName: "Account Verification"
        })
        .when(ROUTES.error, {
            templateUrl: getTemplateUrl("error.html"),
            pageName: "Error"
        })
        .when(ROUTES.register, {
            templateUrl: getTemplateUrl("register.html"),
            pageName: "Register"
        })
        .when(ROUTES.profile, {
            templateUrl: getTemplateUrl("profile.html"),
            resolve: resolve,
            pageName: "Profile"
        })
        .when(ROUTES.accountConfirm, {
            templateUrl: getTemplateUrl("accountConfirm.html"),
            pageName: "Registration Complete"
        })
        .when(ROUTES.deleteAccount, {
            templateUrl: getTemplateUrl("deleteAccount.html"),
            resolve: resolve,
            pageName: "Delete Account"
        })
        .otherwise({
            templateUrl: getTemplateUrl("login.html"),
            pageName: "Login"
        });

If it works properly then you have something fishy going on in the StartCtrl controller. If still doesn't work properly, try this:

$routeProvider
        .when('/', {
            template: '<div>Hello</div>'
        })
        .otherwise('/');

If works properly then look for $routeChangeErrorhandlers. If not then might be any router event handler. Here is the list of event for the ngRoute: $routeChangeStart, $routeChangeError, $routeUpdate, $routeChangeSuccess

EDIT:

Since you don't want users to return back to / route when they click go back you can remove it from history. Just call replace method on $location service when you redirect users away from / route. E.g. $location.path('/login').replace().

like image 130
Alexander Mokin Avatar answered Oct 21 '22 07:10

Alexander Mokin