web-dev-qa-db-de.com

stoppen Sie die Navigation über den angle-ui-router, bis das Versprechen gelöst ist

Ich möchte ein Flackern verhindern, das auftritt, wenn ein Timeout von Rails auftritt, aber eckig weiß es nicht bis zum nächsten Autorisierungsfehler von einer Ressource.

Was passiert ist, dass die Vorlage gerendert wird, einige Ajax-Aufrufe nach Ressourcen erfolgen und wir dann zu Rails weitergeleitet werden, um sich anzumelden. Ich würde lieber bei jeder Zustandsänderung einen Ping an Rails machen, und wenn die Rails-Sitzung abgelaufen ist, werde ich sofort umleiten, BEVOR die Vorlage gerendert wird.

der ui-router hat eine Auflösung, die auf jede Route gesetzt werden kann, aber das scheint DRY überhaupt nicht.

Was ich habe, ist das hier. Das Versprechen wird jedoch erst gelöst, wenn der Staat bereits umgestellt ist. 

$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
        //check that user is logged in
        $http.get('/api/ping').success(function(data){
          if (data.signed_in) {
            $scope.signedIn = true;
          } else {
            window.location.href = '/Rails/devise/login_path'
          }
        })

    });

Wie kann ich den Zustandsübergang unterbrechen, bevor die neue Vorlage basierend auf dem Ergebnis eines Versprechens dargestellt wird?

45
Homan

Ich weiß, dass dies extrem spät ist, aber ich wollte meine Meinung äußern und besprechen, was meiner Meinung nach eine ausgezeichnete Möglichkeit ist, um einen Zustandswechsel zu "pausieren". Gemäß der Dokumentation des angle-ui-router muss jedes Mitglied des "Auflösungs" -Objekts des zu versprechenden Staates aufgelöst werden, bevor der Ladevorgang des Zustands abgeschlossen ist. Meine funktionale (wenn auch noch nicht gereinigte und perfektionierte) Lösung besteht darin, dem Auflösungsobjekt des "toState" auf "$ stateChangeStart" ein Versprechen hinzuzufügen:

zum Beispiel:

$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
    toState.resolve.promise = [
        '$q',
        function($q) {
            var defer = $q.defer();
            $http.makeSomeAPICallOrWhatever().then(function (resp) {
                if(resp = thisOrThat) {
                    doSomeThingsHere();
                    defer.resolve();
                } else {
                    doOtherThingsHere();
                    defer.resolve();
                }
            });
            return defer.promise;
        }
    ]
});

Dadurch wird sichergestellt, dass die Zustandsänderung für die Auflösung des Versprechens gilt. Dies wird ausgeführt, wenn der API-Aufruf beendet ist und alle Entscheidungen getroffen wurden, die auf der Rückgabe von der API basieren. Ich habe dies verwendet, um den Anmeldestatus auf der Serverseite zu überprüfen, bevor eine neue Seite aufgerufen werden kann. Wenn der API-Aufruf aufgelöst wird, verwende ich entweder "event.preventDefault ()", um die ursprüngliche Navigation anzuhalten und dann zur Anmeldeseite (die den gesamten Codeblock umgibt mit einem if state.name! = "Login") weiterzuleiten oder den Benutzer zuzulassen Um fortzufahren, indem Sie einfach das verzögerte Versprechen auflösen, anstatt zu versuchen, Bypass-Booleans zu verwenden undDefault () zu verhindern.

Ich bin mir sicher, dass das Originalposter ihre Ausgabe längst herausgefunden hat, aber ich hoffe wirklich, dass dies jemandem da draußen hilft.

EDIT

Ich dachte mir, ich wollte die Leute nicht irreführen. So sollte der Code aussehen, wenn Sie nicht sicher sind, ob in Ihren Statusobjekten Auflösungsobjekte vorhanden sind:

$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
    if (!toState.resolve) { toState.resolve = {} };
    toState.resolve.pauseStateChange = [
        '$q',
        function($q) {
            var defer = $q.defer();
            $http.makeSomeAPICallOrWhatever().then(function (resp) {
                if(resp = thisOrThat) {
                    doSomeThingsHere();
                    defer.resolve();
                } else {
                    doOtherThingsHere();
                    defer.resolve();
                }
            });
            return defer.promise;
        }
    ]
});

EDIT 2

damit dies für Zustände funktioniert, die keine Definition für die Auflösung haben, müssen Sie dies in der app.config hinzufügen:

   var $delegate = $stateProvider.state;
        $stateProvider.state = function(name, definition) {
            if (!definition.resolve) {
                definition.resolve = {};
            }

            return $delegate.apply(this, arguments);
        };

if (!toState.resolve) { toState.resolve = {} }; in stateChangeStart zu tun scheint nicht zu funktionieren, ich glaube, der ui-router akzeptiert kein Auflösungsdikt, nachdem er initialisiert wurde.

46
Joe.Flanigan

Ich glaube, Sie suchen event.preventDefault()

Hinweis: Verwenden Sie event.preventDefault (), um den Übergang zu verhindern.

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

Ich würde wahrscheinlich resolve in state config als @charlietfl vorschlagen

EDIT:

so hatte ich die Chance, reflectDefault () im Statusänderungsereignis zu verwenden, und Folgendes habe ich getan:

.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

Neuere Dokumentation enthält ein Beispiel, wie Benutzer sync() fortfahren sollten, nachdem preventDefault aufgerufen wurde. Exaple verwendet jedoch $locationChangeSuccess -Ereignis, das für mich und Kommentatoren nicht funktioniert. Verwenden Sie stattdessen $stateChangeStart wie im folgenden Beispiel aus Dokumenten mit einer aktualisierten Veranstaltung:

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();
        });
    });
27

Hier ist meine Lösung für dieses Problem. Es funktioniert gut und ist im Geist einiger anderer Antworten hier. Es ist nur ein bisschen aufgeräumt. Ich setze eine benutzerdefinierte Variable namens 'stateChangeBypass' im Root-Bereich, um unendliches Looping zu verhindern. Ich überprüfe auch, ob der Status 'login' ist und wenn ja, das ist immer erlaubt.

function ($rootScope, $state, Auth) {

    $rootScope.$on('$stateChangeStart', function (event, toState, toParams) {

        if($rootScope.stateChangeBypass || toState.name === 'login') {
            $rootScope.stateChangeBypass = false;
            return;
        }

        event.preventDefault();

        Auth.getCurrentUser().then(function(user) {
            if (user) {
                $rootScope.stateChangeBypass = true;
                $state.go(toState, toParams);
            } else {
                $state.go('login');
            }
        });

    });
}
24
rmberg

da $ urlRouter.sync () nicht mit stateChangeStart funktioniert, ist hier eine 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
        }
    });
15
oori

Die on-Methode gibt a deregistration function for this listener zurück.

So können Sie folgendes tun:

var unbindStateChangeEvent = $scope.$on('$stateChangeStart', 
  function(event, toState, toParams) { 
    event.preventDefault(); 

    waitForSomething(function (everythingIsFine) {
      if(everythingIsFine) {
        unbindStateChangeEvent();
        $state.go(toState, toParams);
      }
    });
});
4
Yann Bertrand

Um die vorhandenen Antworten hier hinzuzufügen, hatte ich genau das gleiche Problem. Wir verwendeten einen Event-Handler im Root-Bereich, um auf $stateChangeStart für meine Berechtigungsbehandlung zu warten. Unglücklicherweise hatte dies den unangenehmen Nebeneffekt, gelegentlich unendliche Verdauungen zu verursachen (keine Ahnung warum, der Code wurde nicht von mir geschrieben).

Die Lösung, die mir einfiel, die eher fehlt, besteht darin, immer den Übergang mit event.preventDefault() zu verhindern, und dann festzustellen, ob der Benutzer über einen asynchronen Anruf angemeldet ist oder nicht. Nachdem Sie dies überprüft haben, wechseln Sie mit $state.go in einen neuen Status. Das wichtige Bit ist jedoch, dass Sie die notify-Eigenschaft für die Optionen in $state.go auf false setzen. Dies verhindert, dass die Zustandsübergänge einen anderen $stateChangeStart auslösen.

 event.preventDefault();
 return authSvc.hasPermissionAsync(toState.data.permission)
    .then(function () {
      // notify: false prevents the event from being rebroadcast, this will prevent us
      // from having an infinite loop
      $state.go(toState, toParams, { notify: false });
    })
    .catch(function () {
      $state.go('login', {}, { notify: false });
    });

Dies ist zwar nicht sehr wünschenswert, aber aufgrund der Art, wie die Berechtigungen in diesem System geladen werden, ist es für mich notwendig. Hätte ich eine synchrone hasPermission verwendet, wurden die Berechtigungen zum Zeitpunkt der Anforderung der Seite möglicherweise nicht geladen. :( Vielleicht könnten wir ui-router nach einer continueTransition-Methode für das Ereignis fragen?

authSvc.hasPermissionAsync(toState.data.permission).then(continueTransition).catch(function() {
  cancelTransition();
  return $state.go('login', {}, { notify: false });
});
4
Dan Pantry

Die vorgeschlagene Lösung von TheRyBerg gefällt mir sehr gut, da Sie alles an einem Ort und ohne zu viele seltsame Tricks machen können. Ich habe herausgefunden, dass es eine Möglichkeit gibt, es noch weiter zu verbessern, so dass Sie stateChangeBypass nicht im Rootscope benötigen. Die Hauptidee ist, dass Sie etwas in Ihrem Code initialisieren möchten, bevor Ihre Anwendung "ausgeführt" werden kann. Wenn Sie sich nur daran erinnern, ob es initialisiert wurde oder nicht, können Sie es folgendermaßen tun:

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

Dann können Sie sich einfach daran erinnern, dass Ihre Daten bereits wie gewünscht im Dienst initialisiert sind, z.

function dataService() {

    var initialized = false;

    return {
        initialize: initialize,
        isInitialized: isInitialized
    }

    function intialize() {

        return $http.get(...)
                    .success(function(response) {
                            initialized=true;
                    });

    }

    function isInitialized() {
        return initialized;
    }
};
2

Sie können die Übergangsparameter von $ stateChangeStart abrufen und sie in einem Dienst unterbringen. Anschließend können Sie den Übergang erneut starten, nachdem Sie sich mit der Anmeldung befasst haben. Sie können auch nach https://github.com/witoldsz/angular-http-auth suchen, wenn Ihre Sicherheit vom Server als http 401-Fehler erfolgt.

1
laurelnaiad
        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);
                });
        });

Ich hatte einige Probleme bei der Verwendung eines booleschen Schutzes, um eine Endlosschleife während stateChangeStart zu vermeiden. Daher überprüfte ich, ob derselbe Übergang erneut versucht wurde, und kehrte sofort zurück, falls dies der Fall war, da das Versprechen immer noch nicht gelöst wurde.

0
GameSalutes

Ich bin auf das gleiche Problem gestoßen. 

angular.module('app', ['ui.router']).run(function($rootScope, $state) {
    yourpromise.then(function(resolvedVal){
        $rootScope.$on('$stateChangeStart', function(event){
           if(!resolvedVal.allow){
               event.preventDefault();
               $state.go('unauthState');
           }
        })
    }).catch(function(){
        $rootScope.$on('$stateChangeStart', function(event){
           event.preventDefault();
           $state.go('unauthState');
           //DO Something ELSE
        })

    });
0
Mohan Kethees