<html>
    
    <head>
        <title>Advanced Angular Navigation</title>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.min.js"></script>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-animate.min.js"></script>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-loader.min.js"></script>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-sanitize.min.js"></script>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-cookies.min.js"></script>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-touch.min.js"></script>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-resource.min.js"></script>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.14.3/ui-bootstrap.min.js"></script>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.14.3/ui-bootstrap-tpls.min.js"></script>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.13/angular-ui-router.min.js"></script>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui-ieshiv.min.js"></script>
        <script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.min.js"></script>
        <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.css">
        <link rel="stylesheet" type="text/css" href="style.css">
        <script type='text/javascript' src="script.js"></script>
        <script type='text/javascript' src="https://rawgit.com/long2know/angular-dialog-service/master/src/dialogService.js"></script>
    </head>    
    <body ng-app="myApp">
        <div ng-include="'menu.html'"></div>
        <div ui-view="main" class="container-fluid"></div>
        <script type="text/ng-template" id="menu.html">
            <nav class="navbar navbar-default" ng-controller="menuCtrl as mn">
              <div class="container-fluid">
                <div class="navbar-header">
                    <span class="navbar-brand">Our Menu</span>
                </div>
                <div>
                  <ul class="nav navbar-nav">
                      <li ng-repeat="m in mn.menuItems track by $index" ng-class="{'active': m && m.active}">
                          <a ng-href="{{ mn.getRouteSref(m) }}" ng-bind="m.heading" />
                        </li>
                      <li>
                          <div class="navbar-hedaer"><span class="navbar-brand">$Watchers: </span><span class="navbar-brand" ng-bind="mn.watchCount" /></div></li>
                  </ul>
                </div>
              </div>
            </nav>
        </script>
        <script type="text/ng-template" id="modalSimple.html">
            <div class="modal-body">
                <h3>Changed states</h3>
                <p>You changed states.</p>
            </div>
            <div class="modal-footer">
                <button class="button primary" ng-click="ok()">Ok</button>
            </div>
        </script>
        <script type="text/ng-template" id="state1.html">
            <div>
                <h1>This is State 1 with no callbacks</h1>
            </div>
            <form name="vm.myForm" novalidate>
        	<div class="content">
        		<div class="form">
        			<label for="firstname">First Name</label><br>
        			<div class="form-group" ng-class="{ 'has-error': vm.myForm.firstname.$invalid }">
        				<input id="firstname" name="firstname" type="text" class="input primary form-control form-control-inline" placeholder="First Name"
        					   ng-model="mf.dialogModel.firstname" ng-required="true" autocomplete="off" />
        				<small id="firstnameReq" class="error" ng-show="vm.myForm.firstname.$error.required">This is a required field.</small>
        			</div>
        			<label for="lastname">Last Name</label><br>
        			<div class="form-group" ng-class="{ 'has-error': vm.myForm.lastname.$invalid }">
        				<input name="lastname" type="text" class="input primary form-control form-control-inline" placeholder="Last Name"
        					   ng-model="mf.dialogModel.lastname" ng-required="true" autocomplete="off" />
        				<small id="lastnameReq" class="error" ng-show="vm.myForm.lastname.$error.required">This is a required field.</small>
        			</div>
        		</div>
        	</div>
        </form>
        </script>
        <script type="text/ng-template" id="state2.html">
            <div>
                <h1>This is State 2 - it has validty callbacks</h1>
            </div>
        <form name="vm.myForm" novalidate>
        	<div class="content">
        		<div class="form">
        			<label for="firstname">First Name</label><br>
        			<div class="form-group" ng-class="{ 'has-error': vm.myForm.firstname.$invalid }">
        				<input id="firstname" name="firstname" type="text" class="input primary form-control form-control-inline" placeholder="First Name"
        					   ng-model="mf.dialogModel.firstname" ng-required="true" autocomplete="off" />
        				<small id="firstnameReq" class="error" ng-show="vm.myForm.firstname.$error.required">This is a required field.</small>
        			</div>
        			<label for="lastname">Last Name</label><br>
        			<div class="form-group" ng-class="{ 'has-error': vm.myForm.lastname.$invalid }">
        				<input name="lastname" type="text" class="input primary form-control form-control-inline" placeholder="Last Name"
        					   ng-model="mf.dialogModel.lastname" ng-required="true" autocomplete="off" />
        				<small id="lastnameReq" class="error" ng-show="vm.myForm.lastname.$error.required">This is a required field.</small>
        			</div>
        		</div>
        	</div>
        </form>
        </script>
        <script type="text/ng-template" id="state3.html">
            <div>
                <h1>This is State 3 with "on navigate" call backs</h1>
            </div>
             <form name="vm.myForm" novalidate>
        	<div class="content">
        		<div class="form">
        			<label for="firstname">First Name</label><br>
        			<div class="form-group" ng-class="{ 'has-error': vm.myForm.firstname.$invalid }">
        				<input id="firstname" name="firstname" type="text" class="input primary form-control form-control-inline" placeholder="First Name"
        					   ng-model="mf.dialogModel.firstname" ng-required="true" autocomplete="off" />
        				<small id="firstnameReq" class="error" ng-show="vm.myForm.firstname.$error.required">This is a required field.</small>
        			</div>
        			<label for="lastname">Last Name</label><br>
        			<div class="form-group" ng-class="{ 'has-error': vm.myForm.lastname.$invalid }">
        				<input name="lastname" type="text" class="input primary form-control form-control-inline" placeholder="Last Name"
        					   ng-model="mf.dialogModel.lastname" ng-required="true" autocomplete="off" />
        				<small id="lastnameReq" class="error" ng-show="vm.myForm.lastname.$error.required">This is a required field.</small>
        			</div>
        		</div>
        	</div>
        </form>
        </script>
        <script type="text/ng-template" id="state4.html">
            <div>
                <h1>This is State 4 with both validity and "on navigate" callbacks</h1>
            </div>
            <form name="vm.myForm" novalidate>
        	<div class="content">
        		<div class="form">
        			<label for="firstname">First Name</label><br>
        			<div class="form-group" ng-class="{ 'has-error': vm.myForm.firstname.$invalid }">
        				<input id="firstname" name="firstname" type="text" class="input primary form-control form-control-inline" placeholder="First Name"
        					   ng-model="mf.dialogModel.firstname" ng-required="true" autocomplete="off" />
        				<small id="firstnameReq" class="error" ng-show="vm.myForm.firstname.$error.required">This is a required field.</small>
        			</div>
        			<label for="lastname">Last Name</label><br>
        			<div class="form-group" ng-class="{ 'has-error': vm.myForm.lastname.$invalid }">
        				<input name="lastname" type="text" class="input primary form-control form-control-inline" placeholder="Last Name"
        					   ng-model="mf.dialogModel.lastname" ng-required="true" autocomplete="off" />
        				<small id="lastnameReq" class="error" ng-show="vm.myForm.lastname.$error.required">This is a required field.</small>
        			</div>
        		</div>
        	</div>
        </form>
        </script>
        <script type="text/ng-template" id="modalErrorTemplate.html">
            <div class="modal-header">
                <h3 ng-bind-html="modalHeader"></h3>
            </div>
            <div class="modal-body" ng-bind-html="modalBody"></div>
            <div class="modal-footer">
                <button class="btn btn-primary" ng-click="ok()">Ok</button>
            </div>
        </script>
        <script type="text/ng-template" id="modalGeneral.html">
            <div class="modal-header">
                <h3 ng-bind-html="modalHeader"></h3>
            </div>
            <div class="modal-body" ng-bind-html="modalBody"></div>
            <div class="modal-footer">
                <input class="btn" type="button" ng-show="hasCancel" data-ng-click="cancel()" value="Cancel">
                <button class="btn btn-primary" ng-click="ok()">Ok</button>
            </div>
        </script>
    </body>
</html>
.modal-dialog {
    margin-top: 20px;
}
(function () {
    angular.module('myApp.services', ['ngResource', 'ngAnimate']);
    angular.module('myApp.controllers', []);

    var myApp = angular.module('myApp', [
		'long2know',
        'myApp.services',
        'myApp.controllers',
        'ngSanitize',
        'ui.bootstrap',
        'ui.router',
        'ui']);

    var watchCountService = function () {
        // I return the count of watchers on the current page.
        function getWatchCount() {
            var total = 0;
            angular.element(".ng-scope").each(

            function countWatchers() {
                var scope = $(this).scope();
                total += scope.$$watchers ? scope.$$watchers.length : 0;
            });

            return (total);
        }

        return {
            getWatchCount: getWatchCount
        };
    };

    var menuService = function ($rootScope, $q, $state, $sce, dialogService) {
        var menuItems = [
            {
                name: 'state1', heading: "State1",
                route: "app.state1",
                active: false
            }, {
                name: 'state2',
                heading: "State2",
                route: "app.state2",
                active: false
            }, {
                name: 'state3',
                heading: "State3",
                route: "app.state3",
                active: false
            }, {
                name: 'state4',
                heading: "State4",
                route: "app.state4",
                active: false
            }];
        var
        currentMenuItem,
        onNavigateFn,
        validationFn,
        resetMenuItem = function (menuItem) {
            menuItem.active = false;
        },
        resetMenuItems = function () {
            for (var i = 0; i < menuItems.length; i++) {
                resetMenuItem(menuItems[i]);
            }
        },
        findMenuItem = function (routeName) {
            var criteriaFunction = function (c) {
                return c.route === routeName || routeName.indexOf(c.route) != -1;
            };
            return menuItems.filter(criteriaFunction)[0];
        };

        function resetCallbacks() {
            onNavigateFn = null;
            validationFn = function () { return true; };
        };

        $rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
            var valdatinFnResponse = validationFn();
            if ((valdatinFnResponse !== true || valdatinFnResponse.error === true)) {
                var errorTitle = 'Validation Error!';
                var errorBody = (valdatinFnResponse.errorMessage) ? valdatinFnResponse.errorMessage : 'Please correct the form errors listed!';
                dialogService.openDialog("modalErrorTemplate.html", ['$scope', '$modalInstance', function ($scope, $modalInstance) {
                    $scope.modalHeader = $sce.trustAsHtml(errorTitle);
                    $scope.modalBody = $sce.trustAsHtml(dialogService.stringFormat("<p><strong>{0}</strong></p>", errorBody));
                    $scope.ok = function () {
                        $modalInstance.close();
                    };
                    $scope.hasCancel = false;
                }]);
                event.preventDefault();
                return;
            };

            if (!onNavigateFn) {
                resetCallbacks();
                currentMenuItem = findMenuItem(toState.name, toParams);
                return;
            }

            event.preventDefault();

            $q.when(onNavigateFn()).then(
                function (result) {
                    resetCallbacks();
                    $state.go(toState, toParams);
                },
                function (error) {
                    return; // we just don't change state
                }
            );
        });

        $rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
            currentMenuItem = findMenuItem(toState.name, toParams);
            if (currentMenuItem) {
                currentMenuItem.active = true;
                prevMenuItem = findMenuItem(fromState.name);
                if (prevMenuItem && prevMenuItem.name !== currentMenuItem.name) {
                    prevMenuItem.active = false;
                }
            } else {
                for (var i = 0; i < menuItems.length; i++) {
                    menuItems[i].active = false;
                }
            }
        });

        resetCallbacks();

        return {
            menuItems: menuItems,
            currentMenuItem: currentMenuItem,
            setOnNavigateCallback: function (onNavigateCb) {
                onNavigateFn = onNavigateCb;
            },
            setValidationCallback: function (validationCb) {
                validationFn = validationCb;
            }
        };
    };

    var state1Controller = function (menuService, dialogService) {
        var vm = this,
           isFormValid = function () {
               return vm.myForm.$valid === true;
           },
           isFormDirty = function () {
               return vm.myForm.$dirty === true;
           },
           navigateCallback = function () {
               if (isFormDirty()) {
                   return dialogService.openDiscardChangesDialog();
               } else {
                   return true;
               };
           },
           init = function () {
           };

        init();
    };

    var state2Controller = function (menuService, dialogService) {
        var vm = this,
            isFormValid = function () {
                return vm.myForm.$valid === true;
            },
            isFormDirty = function () {
                return vm.myForm.$dirty === true;
            },
            navigateCallback = function () {
                if (isFormDirty()) {
                    return dialogService.openDiscardChangesDialog();
                } else {
                    return true;
                };
            },
            init = function () {
                menuService.setValidationCallback(isFormValid);
            };

        init();
    };

    var state3Controller = function (menuService, dialogService) {
        var vm = this,
            isFormValid = function () {
                return vm.myForm.$valid === true;
            },
            isFormDirty = function () {
                return vm.myForm.$dirty === true;
            },
            navigateCallback = function () {
                if (isFormDirty()) {
                    return dialogService.discardChangesDialog();
                } else {
                    return true;
                };
            },
            init = function () {
                menuService.setOnNavigateCallback(navigateCallback);
            };

        init();
    };

    var state4Controller = function (menuService, dialogService) {
        var vm = this,
            isFormValid = function () {
                return vm.myForm.$valid === true;
            },
            isFormDirty = function () {
                return vm.myForm.$dirty === true;
            },
            navigateCallback = function () {
                if (isFormDirty()) {
                    return dialogService.openDiscardChangesDialog();
                } else {
                    return true;
                };
            },
            init = function () {
                menuService.setOnNavigateCallback(navigateCallback);
                menuService.setValidationCallback(isFormValid);
            };

        init();
    };

    var menuController = function ($state, $scope, menuService, watchCountService) {
        var vm = this;
        vm.menuItems = menuService.menuItems;
        vm.watchCount = 0;

        $scope.$watch(

        function watchCountExpression() {
            return (watchCountService.getWatchCount());
        },

        function handleWatchCountChange(newValue) {
            vm.watchCount = newValue;
        });
        vm.getRouteSref = function (menuItem) {
            if (!menuItem) {
                menuItem = vm.menuItems[0]
            };
            return $state.href(menuItem.route);
        };
    };   

    menuService.$inject = ['$rootScope', '$q', '$state', '$sce', 'dialogService'];
    angular.module('myApp.services')
        .factory('menuService', menuService);

    state1Controller.$inject = ['menuService', 'dialogService'];
    angular.module('myApp.controllers')
        .controller('state1Ctrl', state1Controller);

    state2Controller.$inject = ['menuService', 'dialogService'];
    angular.module('myApp.controllers')
        .controller('state2Ctrl', state2Controller);

    state3Controller.$inject = ['menuService', 'dialogService'];
    angular.module('myApp.controllers')
        .controller('state3Ctrl', state3Controller);

    state4Controller.$inject = ['menuService', 'dialogService'];
    angular.module('myApp.controllers')
        .controller('state4Ctrl', state4Controller);

    menuController.$inject = ['$state', '$scope', 'menuService', 'watchCountService'];
    angular.module('myApp.controllers')
        .controller('menuCtrl', menuController);

    angular.module('myApp.services')
        .factory('watchCountService', watchCountService);

    myApp.config(['$uibModalProvider', '$locationProvider', '$stateProvider', '$urlRouterProvider',

    function ($uibModalProvider, $locationProvider, $stateProvider, $urlRouterProvider) {
        $uibModalProvider.options = { animation: true, backdrop: 'static', keyboard: false };
        $locationProvider.html5Mode(false);

        $urlRouterProvider.when('/', '/state1')
            .otherwise("/state1");

        $stateProvider.state('app', {
            abstract: true,
            url: '/',
            views: {
                'main': {
                    template: '<div ui-view>/div>'
                }
            }
        })
        .state('app.state1', {
            url: 'state1',
            templateUrl: 'state1.html',
            controller: 'state1Ctrl',
            controllerAs: 'vm',
            reloadOnSearch: false
        })
        .state('app.state2', {
            url: 'state2',
            templateUrl: 'state2.html',
            controller: 'state2Ctrl',
            controllerAs: 'vm',
            reloadOnSearch: false
        })
        .state('app.state3', {
            url: 'state3',
            templateUrl: 'state3.html',
            controller: 'state3Ctrl',
            controllerAs: 'vm',
            reloadOnSearch: false
        })
            .state('app.state4', {
                url: 'state4',
                templateUrl: 'state4.html',
                controller: 'state4Ctrl',
                controllerAs: 'vm',
                reloadOnSearch: false
            })
    }]);

    myApp.run(['$log', function ($log) {
        $log.log("Start.");
    }]);
})()

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.