stop angular-ui-router navigation until promise is resolved

前端 未结 10 1904
情歌与酒
情歌与酒 2020-12-01 02:44

I want to prevent some flickering that happens when rails devise timeout occurs, but angular doesn\'t know until the next authorization error from a resource.

What h

相关标签:
10条回答
  • 2020-12-01 03:21
            var lastTransition = null;
            $rootScope.$on('$stateChangeStart',
                function(event, toState, toParams, fromState, fromParams, options)  {
                    // state change listener will keep getting fired while waiting for promise so if detect another call to same transition then just return immediately
                    if(lastTransition === toState.name) {
                        return;
                    }
    
                    lastTransition = toState.name;
    
                    // Don't do transition until after promise resolved
                    event.preventDefault();
                    return executeFunctionThatReturnsPromise(fromParams, toParams).then(function(result) {
                        $state.go(toState,toParams,options);
                    });
            });
    

    I had some issues using a boolean guard for avoiding infinite loop during stateChangeStart so took this approach of just checking if the same transition was attempted again and returning immediately if so since for that case the promise has still not resolved.

    0 讨论(0)
  • 2020-12-01 03:25

    I believe you are looking for event.preventDefault()

    Note: Use event.preventDefault() to prevent the transition from happening.

    $scope.$on('$stateChangeStart', 
    function(event, toState, toParams, fromState, fromParams){ 
            event.preventDefault(); 
            // transitionTo() promise will be rejected with 
            // a 'transition prevented' error
    })
    

    Although I would probably use resolve in state config as @charlietfl suggested

    EDIT:

    so I had a chance to use preventDefault() in state change event, and here is what I did:

    .run(function($rootScope,$state,$timeout) {
    
    $rootScope.$on('$stateChangeStart',
        function(event, toState, toParams, fromState, fromParams){
    
            // check if user is set
            if(!$rootScope.u_id && toState.name !== 'signin'){  
                event.preventDefault();
    
                // if not delayed you will get race conditions as $apply is in progress
                $timeout(function(){
                    event.currentScope.$apply(function() {
                        $state.go("signin")
                    });
                },300)
            } else {
                // do smth else
            }
        }
    )
    
    }
    

    EDIT

    Newer documentation includes an example of how one should user sync() to continue after preventDefault was invoked, but exaple provided there uses $locationChangeSuccess event which for me and commenters does not work, instead use $stateChangeStart as in the example below, taken from docs with an updated event:

    angular.module('app', ['ui.router'])
        .run(function($rootScope, $urlRouter) {
            $rootScope.$on('$stateChangeStart', function(evt) {
                // Halt state change from even starting
                evt.preventDefault();
                // Perform custom logic
                var meetsRequirement = ...
                // Continue with the update and state transition if logic allows
                if (meetsRequirement) $urlRouter.sync();
            });
        });
    
    0 讨论(0)
  • 2020-12-01 03:26

    I really like the suggested solution by TheRyBerg, since you can do all in one place and without too much weird tricks. I have found that there is a way to improve it even further, so that you don't need the stateChangeBypass in the rootscope. The main idea is that you want to have something initialized in your code before your application can "run". Then if you just remember if it's initialized or not you can do it this way:

    rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState) {
    
        if (dataService.isInitialized()) {
            proceedAsUsual(); // Do the required checks and redirects here based on the data that you can expect ready from the dataService
        } 
        else {
    
            event.preventDefault();
    
            dataService.intialize().success(function () {
                    $state.go(toState, toParams);
            });
        }
    });
    

    Then you can just remember that your data is already initialized in the service the way you like, e.g.:

    function dataService() {
    
        var initialized = false;
    
        return {
            initialize: initialize,
            isInitialized: isInitialized
        }
    
        function intialize() {
    
            return $http.get(...)
                        .success(function(response) {
                                initialized=true;
                        });
    
        }
    
        function isInitialized() {
            return initialized;
        }
    };
    
    0 讨论(0)
  • 2020-12-01 03:34

    as $urlRouter.sync() doesn't work with stateChangeStart, here's an alternative:

        var bypass;
        $rootScope.$on('$stateChangeStart', function(event,toState,toParams) {
            if (bypass) return;
            event.preventDefault(); // Halt state change from even starting
            var meetsRequirement = ... // Perform custom logic
            if (meetsRequirement) {  // Continue with the update and state transition if logic allows
                bypass = true;  // bypass next call
                $state.go(toState, toParams); // Continue with the initial state change
            }
        });
    
    0 讨论(0)
提交回复
热议问题