Search code examples
angularjsfirebaseangularfirefirebase-hosting

How do I resolve deep links using Angular (AngularFire) and Firebase


Disambiguation:

This issue is not because rewrites have somehow been misconfigured in the firebase.json. The Firebase docs clearly state how to do this here. The problem is that only shallow links work; deep links do not.

Problem:

Deep links do not work after turning on html5Mode using AngularFire with Firebase: First level pages load; any other page will not. So /login works but /supplier/accounts does not.

config.js:

function configState($stateProvider, $urlRouterProvider, $compileProvider, $httpProvider) {
    // Optimize load start with remove binding information inside the DOM element
    $compileProvider.debugInfoEnabled(true);

    // Set default state
    $urlRouterProvider.otherwise("/login");

    $stateProvider
        /* 
         * Common Views
         */
        .state('common', {
            abstract: true,
            templateUrl: "views/common/content_empty.html",
            data: {
                pageTitle: 'Common'
            }
        })
        .state('common.login', {
            url: "/login",
            templateUrl: "views/common_app/login.html",
            data: {
                pageTitle: 'Login page',
                specialClass: 'blank'
            }
        })
        .state('common.logout', {
            url: "/logout",
            templateUrl: "views/common_app/logout.html",
            data: {
                pageTitle: 'Logout page',
                specialClass: 'blank'
            },
            resolve: {
                // controller will not be loaded until $requireAuth resolves
                "currentAuth": ["$state", "Auth", "loginRedirectPath", function($state, Auth, loginRedirectPath) {
                    return Auth.$requireAuth();
                }]
            }
        })

        /* 
         * Secure Abstract Class
         */
        .state('secure', {
            abstract: true,
            templateUrl: "views/common/content_empty.html",
            resolve: {
                // controller will not be loaded until $requireAuth resolves
                "currentAuth": ["$state", "Auth", "loginRedirectPath", function($state, Auth, loginRedirectPath) {
                    return Auth.$requireAuth();
                }]
            }
        })   


        /* 
         * Supplier Accounts
         */
        .state('secure.account', {
            abstract: true,
            url: "/supplier",
            templateUrl: "views/common/content.html",
            data: {
                pageTitle: 'Supplier'
            }
        })       
        .state('secure.account.accounts', {
            url: "/accounts",
            templateUrl: "views/account/accounts.html",
            controller: "suppliersListCtrl as suppliersListCtrl",
            data: {
                pageTitle: 'Accounts',
                pageDesc: 'Accounts assiged to this User'
            }
        })
};

angular
    .module('app')
    // for ui-router
    .run(["$rootScope", "$state", function($rootScope, $state) {
        $rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
            // Catch the error thrown when the $requireAuth promise is rejected
            // redirect the user back to the login page
            if (error === "AUTH_REQUIRED") {
                console.log("AUTH_REQUIRED Fired!!!");
                $state.go("common.login");
            }
        });

        // Log ui-router errors in the console
        $rootScope.$on("$stateChangeError", console.log.bind(console));
    }])
    .factory("Auth", ["$firebaseAuth", "FIREBASE_URI",
        function($firebaseAuth, FIREBASE_URI) {
            var ref = new Firebase(FIREBASE_URI);
            return $firebaseAuth(ref);
        }
    ])
    .config(["$locationProvider", function($locationProvider) {
        $locationProvider.html5Mode(
            {
                enabled: true,
                requireBase: false
            });
    }])
    .constant('FIREBASE_URI', '<FIRE BASE URI HERE>')
    .constant('loginRedirectPath', 'common.login')
    .config(configState)

firebase.json:

{
    "firebase": "firebase-app",
    "public": "main",
    "rewrites": [{
        "source": "**",
        "destination": "/index.html"
    }],
    "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
    ]
}

Solution

  • (Took a look at the bug fix I pushed so this question doesn't remain unanswered.)

    Turns out it was my Gruntfile. I needed to update my LiveReload config in my Grunt server settings. Your LiveReload changes should look like this:

    // Grunt configuration
    grunt.initConfig({
    
        // Project settings
        your_application_name: appConfig,
    
        // The grunt server settings
        connect: {
            options: {
                port: 8000,
                hostname: 'localhost',
                livereload: 35729
            },
            livereload: {
                options: {
                    open: true,
                    middleware: function (connect) {
                        return [
                            modRewrite(['^[^\\.]*$ /index.html [L]']),
                            connect.static('.tmp'),
                            connect().use(
                                '/bower_components',
                                connect.static('./bower_components')
                            ),
                            connect.static(appConfig.app)
                        ];
                    }
                }
            }
            //...
        }
    });