2014-05-06 10 views
5

È possibile passare una promessa a un UI.Router $state da un controller esterno (ad esempio, il controller che ha attivato lo stato)?Passa una promessa in un controller di stato Angular-UI

So che $state.go() restituisce una promessa; è possibile ignorare che con la propria promessa risolvere direttamente questa promessa o risolverlo con una nuova promessa?

Inoltre, la documentazione dice il promessa restituito da $state.go() può essere respinta con un'altra promessa (indicato da transition superseded), ma non riesco a trovare da nessuna parte che indica come questo può essere fatto all'interno dello Stato stesso.

Ad esempio, nel codice seguente, vorrei poter aspettare che l'utente faccia clic su un pulsante ($scope.buttonClicked()) prima di continuare su doSomethingElse().

So che posso emettere un evento, ma dal momento che le promesse sono infuse in Angular così profondamente, mi sono chiesto se c'era un modo per farlo attraverso promise.resolve/promise.reject.

angular.module('APP', ['ui.router']) 
.config(['$stateProvider', function ($stateProvider) { 
    $stateProvider 
    .state('myState', { 
     template: '<p>myState</p>', 
     controller: ['$state', '$scope', '$q', function ($state, $scope, $q) { 
      var deferred = $q.defer(); 

      $scope.buttonClicked = function() { 
       deferred.resolve(); 
      } 
     }] 
    }); 
}]) 
.controller('mainCtrl', ['$state', function ($state) { 
    $state.go('myState') 
    .then(doSomethingElse) 
}]); 

Aggiornamento ho accettato la risposta di @ BLINT come mi vicina ha avuto modo di quello che volevo. Di seguito è riportato un codice che arricchisce l'idea di questa risposta un po 'di più. Non penso che il modo in cui ho scritto questa sia una soluzione molto elegante e sono felice che qualcuno possa suggerire un modo migliore per risolvere le promesse da uno stato innescato.

La soluzione che ho scelto è quello della catena tue promesse come si farebbe normalmente nel controller, ma lasciare un $scope.next() metodo (o qualcosa di simile) collegato a tale ambito che risolve/respinge la promessa. Poiché lo stato può ereditare l'ambito del controller chiamante, sarà in grado di invocare direttamente quel metodo e quindi risolvere/rifiutare la promessa. Ecco come potrebbe funzionare:

In primo luogo, impostare il tuo Stati con pulsanti/controller che richiedono un metodo $scope.next():

.config(function ($stateProvider) { 
    $stateProvider 
    .state('selectLanguage', { 
     template: '<p>Select language for app: \ 
      <select ng-model="user.language" ng-options="language.label for language in languages">\ 
       <option value="">Please select</option>\ 
      </select>\ 
      <button ng-click="next()">Next</button>\ 
      </p>', 
     controller: function ($scope) { 
      $scope.languages = [ 
       {label: 'Deutch', value: 'de'}, 
       {label: 'English', value: 'en'}, 
       {label: 'Français', value: 'fr'}, 
       {label: 'Error', value: null} 
      ]; 
     } 
    }) 
    .state('getUserInfo', { 
     template: '<p>Name: <input ng-model="user.name" /><br />\ 
      Email: <input ng-model="user.email" /><br />\ 
      <button ng-click="next()">Next</button>\ 
      </p>' 
    }) 
    .state('mainMenu', { 
     template: '<p>The main menu for {{user.name}} is in {{user.language.label}}</p>' 
    }) 
    .state('error', { 
     template: '<p>There was an error</p>' 
    }); 
}) 

Successivamente, è istituito il controller. In questo caso, sto usando un metodo di servizio locale, user.loadFromLocalStorage() per far rotolare la palla (restituisce una promessa), ma qualsiasi promessa lo farà. In questo flusso di lavoro, se lo $scope.user manca qualcosa, verrà progressivamente popolato utilizzando gli stati. Se è completamente popolato, salta direttamente al menu principale. Se gli elementi vengono lasciati vuoti o si trovano in uno stato non valido, viene visualizzata una vista di errore.

.controller('mainCtrl', function ($scope, $state, $q, User) { 
    $scope.user = new User(); 

    $scope.user.loadFromLocalStorage() 
    .then(function() { 
     var deferred; 

     if ($scope.user.language === null) { 
      deferred = $q.defer(); 

      $state.go('selectLanguage'); 

      $scope.next = function() { 
       $scope.next = undefined; 

       if ($scope.user.language === null) { 
        return deferred.reject('Language not selected somehow'); 
       } 

       deferred.resolve(); 
      }; 

      return deferred.promise; 
     } 
    }) 
    .then(function() { 
     var deferred; 

     if ($scope.user.name === null || $scope.user.email === null) { 
      deferred = $q.defer(); 

      $state.go('getUserInfo'); 
      $scope.next = function() { 
       $scope.next = undefined; 

       if ($scope.user.name === null || $scope.user.email === null) { 
        return deferred.reject('Could not get user name or email'); 
       } 

       deferred.resolve(); 
      }; 

      return deferred.promise; 
     } 


    }) 
    .then(function() { 
     $state.go('mainMenu'); 
    }) 
    .catch(function (err) { 
     $state.go('error', err); 
    }); 

}); 

questo è abbastanza verbose e non ancora molto secco, ma dimostra l'intenzione generale di controllo del flusso asincrono mediante promesse.

+0

Cosa intendi con 'è possibile scavalcarlo con la tua stessa promessa?'? – blint

+0

Siamo spiacenti. Immagino che il modo migliore per dire che sia, puoi risolvere una promessa "$ stato" con la tua stessa promessa. – Andrew

risposta

3

Lo scopo di promises è garantire un risultato ... o gestire un errore. Le promesse possono essere concatenate, restituite in funzioni e quindi estese.

Non avresti alcun interesse a "annullare" una promessa. Cosa si può fare, tuttavia:

  • Gestire il caso di errore.Ecco l'esempio dalla documentazione:
promiseB = promiseA.then(function(result) { 
    // success: do something and resolve promiseB 
    //   with the old or a new result 
    return result; 
    }, function(reason) { 
    // error: handle the error if possible and 
    //  resolve promiseB with newPromiseOrValue, 
    //  otherwise forward the rejection to promiseB 
    if (canHandle(reason)) { 
    // handle the error and recover 
    return newPromiseOrValue; 
    } 
    return $q.reject(reason); 
    }); 
  • appendere una nuova operazione asincrona nella catena promessa. Puoi combinare le promesse. Se un metodo chiamato nella catena restituisce una promessa, il genitore promesso metterà a muro il resto della catena una volta risolta la nuova promessa.

Ecco il modello che potrebbe essere alla ricerca di:

angular.module('APP', ['ui.router']) 
.config(['$stateProvider', function ($stateProvider) { 
    $stateProvider 
    .state('myState', { 
     template: '<p>myState</p>', 
     controller: 'myCtrl' 
    }); 
}]) 
.controller('myCtrl', ['$scope', '$state', '$q', '$http', 'someAsyncServiceWithCallback', 
    function ($scope, $state, $q, $http, myService) { 
    $scope.buttonClicked = function() { 
     $state.go('myState') 
     .then(function() { 
      // You can return a promise... 
      // From a method that returns a promise 
      // return $http.get('/myURL'); 

      // Or from an old-school method taking a callback: 
      var deferred = $q.defer(); 
      myService(function(data) { 
       deferred.resolve(data); 
      }); 

      return deferred.promise; 
     }, 
     function() { 
      console.log("$state.go() failed :("); 
     }); 
    }; 
}]); 
+0

Molto ben scritto. Un commento però, $ scope.buttenClicked risolve il rinvio lì, questo può accadere solo _solo_ e i clic successivi saranno semplicemente ignorati, penso che valga la pena di chiarire. –

+0

@BenjaminGruenbaum fa riferimento anche a questo, ma la promessa restituita da '$ state.go ('myState')' ritorna immediatamente (beh, non appena lo stato è stato attivato e la vista aggiornata), non quando '$ scope. buttonClicked() 'è invocato. Questo è il collegamento che spero di fare. – Andrew

+0

Le promesse vengono restituite immediatamente, ma puoi effettivamente accedere al loro valore (chiamato "scartare" la promessa) tramite '.then' –

1

Forse un modo per raggiungere questo sarebbe quello di restituire la promessa da parte dello Stato resolve

resolve: { 
    myResolve: function($scope, $q) { 
     var deferred = $q.defer(); 
     $scope.buttonClicked = function() { 
      deferred.resolve(); 
     } 
     return deferred.promise; 
    } 
} 

C'è anche un esempio in resolve docs che può interessare

// Another promise example. If you need to do some 
// processing of the result, use .then, and your 
// promise is chained in for free. This is another 
// typical use case of resolve. 
promiseObj2: function($http){ 
    return $http({method: 'GET', url: '/someUrl'}) 
     .then (function (data) { 
      return doSomeStuffFirst(data); 
     }); 
}, 
+0

Ho appena provato questo, ed è molto vicino a quello che mi serve. Sfortunatamente, '$ scope' non esiste ancora nel blocco' resolve' (credo sia equivalente a un blocco 'config'). Pertanto, non posso associare un'azione nel modello di visualizzazione o il controller di stato a una dipendenza 'resolve'. – Andrew

Problemi correlati