webentwicklung-frage-antwort-db.com.de

Wenden Sie das Laden-Spinner während der Auflösung des UI-Routers an

Die resolve-Eigenschaft von $routeProvider ermöglicht die Ausführung einiger Jobs, BEVOR die entsprechende Ansicht gerendert wird. 

Was ist, wenn ich einen Spinner anzeigen möchte, während die Jobs ausgeführt werden, um die Benutzererfahrung zu verbessern?
Andernfalls würde der Benutzer der Meinung sein, dass die Anwendung blockiert wurde, da zum Beispiel für einige Millisekunden> 600 keine Ansichtselemente angezeigt wurden.

Natürlich gab es die Möglichkeit, ein globales div-Element aus der aktuellen Ansicht zur Anzeige zu definieren, um den Spinner dank der $scope.$rootChangeStart-Funktion anzuzeigen.
Aber ich möchte nicht die ganze Seite mit einem schlechten Spinner in der Mitte verstecken.
Ich möchte, dass sich einige Seiten meiner Webapp hinsichtlich der Darstellung des Ladens unterscheiden.

Ich bin auf diesen interessanten Beitrag gestoßen, der genau das oben beschriebene Problem enthält:

Dieser Ansatz führt zu einer schrecklichen Benutzeroberfläche. Der Benutzer klickt auf eine Schaltfläche, um eine Liste oder etwas zu aktualisieren, und der gesamte Bildschirm wird angezeigt mit einem generischen Spinner belegt, da die Bibliothek keine Möglichkeit hat, Zeigt einen Spinner nur für die Ansicht (en) an, die tatsächlich von .__ betroffen sind. der Zustandswechsel. Nein Danke.

Nachdem ich dieses Problem eingereicht hatte, wurde mir auf jeden Fall klar, dass die "Auflösung" Feature ist ein Anti-Muster. Es wartet, bis alle Versprechen gelöst sind animiert dann die Zustandsänderung. Das ist völlig falsch - Sie wollen Ihre Übergangsanimationen zwischen Zuständen werden parallel zu Ihren Daten ausgeführt lasten, so dass letztere durch erstere verdeckt werden können.

Stellen Sie sich zum Beispiel vor, Sie haben eine Liste mit Elementen und klicken auf eines von Sie blenden die Liste aus und zeigen die Details des Elements in einer anderen Ansicht an. Wenn wir eine asynchrone Last für die Artikeldetails haben, dauert das im Durchschnitt 400ms, dann können wir die Last in den meisten Fällen fast vollständig mit .__ abdecken. eine 300ms "Verlassen" -Animation in der Listenansicht und eine 300ms "Enter" Animation in der Artikeldetailansicht. Auf diese Weise sorgen wir für ein glatteres Gefühl der Benutzeroberfläche und kann in den meisten Fällen überhaupt keinen Spinner anzeigen.

Dies setzt jedoch voraus, dass wir die asynchrone Last und den Status .__ initiieren. Animation im selben Moment ändern. Wenn wir "Auflösen" verwenden, dann die Die gesamte asynchrone Animation erfolgt vor dem Beginn der Animation. Der Benutzer klickt, sieht einen Spinner und dann die Übergangsanimation. Das Ganze Die Zustandsänderung dauert ~ 1000ms, was zu langsam ist.

"Auflösen" könnte eine nützliche Methode sein, um Abhängigkeiten zwischen .__ zwischenzuspeichern. verschiedene Ansichten, wenn die Möglichkeit bestand, nicht auf Versprechen zu warten, sondern die aktuelles Verhalten, immer vor dem Zustandswechsel aufzulösen macht es fast nutzlos, IMO. Es sollte für alle .__ vermieden werden. Abhängigkeiten, die asynchrone Ladevorgänge beinhalten.

Soll ich wirklich resolve verwenden, um Daten zu laden, und sie lieber direkt in die entsprechende Steuerung laden? Damit ich die entsprechende Ansicht aktualisieren kann, solange der Job ausgeführt wird und an der gewünschten Stelle in der Ansicht, nicht global.

35
Mik378

Sie können eine Direktive verwenden, die auf $routeChangeStart wartet und beispielsweise das Element anzeigt, wenn es ausgelöst wird:

app.directive('showDuringResolve', function($rootScope) {

  return {
    link: function(scope, element) {

      element.addClass('ng-hide');

      var unregister = $rootScope.$on('$routeChangeStart', function() {
        element.removeClass('ng-hide');
      });

      scope.$on('$destroy', unregister);
    }
  };
});

Dann platzieren Sie es im Lader der jeweiligen Ansicht, zum Beispiel:

Ansicht 1:

<div show-during-resolve class="alert alert-info">
  <strong>Loading.</strong>
  Please hold.
</div>

Ansicht 2:

<span show-during-resolve class="glyphicon glyphicon-refresh"></span>

Das Problem bei dieser Lösung (und bei vielen anderen Lösungen für diese Angelegenheit) besteht darin, dass beim Aufrufen einer der Routen von einer externen Website keine vorherige ng-view-Vorlage geladen wird. Daher ist Ihre Seite während der Auflösung möglicherweise leer.

Dies kann gelöst werden, indem eine Direktive erstellt wird, die als Fallback-Loader fungiert. Es hört auf $routeChangeStart und zeigt nur dann einen Loader an, wenn es keine vorherige Route gibt.

Ein grundlegendes Beispiel:

app.directive('resolveLoader', function($rootScope, $timeout) {

  return {
    restrict: 'E',
    replace: true,
    template: '<div class="alert alert-success ng-hide"><strong>Welcome!</strong> Content is loading, please hold.</div>',
    link: function(scope, element) {

      $rootScope.$on('$routeChangeStart', function(event, currentRoute, previousRoute) {
        if (previousRoute) return;

        $timeout(function() {
          element.removeClass('ng-hide');
        });
      });

      $rootScope.$on('$routeChangeSuccess', function() {
        element.addClass('ng-hide');
      });
    }
  };
});

Der Fallback Loader wird mit ng-view außerhalb des Elements platziert:

<body>
  <resolve-loader></resolve-loader>
  <div ng-view class="fadein"></div>
</body>

Demo von allem:http://plnkr.co/edit/7clxvUtuDBKfNmUJdbL3?p=preview

50
tasseKATT

ich denke, das ist ziemlich ordentlich

app.run(['$rootScope', '$state',function($rootScope, $state){

  $rootScope.$on('$stateChangeStart',function(){
      $rootScope.stateIsLoading = true;
 });


  $rootScope.$on('$stateChangeSuccess',function(){
      $rootScope.stateIsLoading = false;
 });

}]);

und dann zu sehen

<div ng-show='stateIsLoading'>
  <strong>Loading.</strong>
</div>
29
Pranay Dutta

Um Pranays Antwort weiter zu bringen, habe ich es so gemacht.

JS:

app.run(['$rootScope',function($rootScope){

    $rootScope.stateIsLoading = false;
    $rootScope.$on('$routeChangeStart', function() {
        $rootScope.stateIsLoading = true;
    });
    $rootScope.$on('$routeChangeSuccess', function() {
        $rootScope.stateIsLoading = false;
    });
    $rootScope.$on('$routeChangeError', function() {
        //catch error
    });

}]);

HTML

<section ng-if="!stateIsLoading" ng-view></section>
<section ng-if="stateIsLoading">Loading...</section>
11
sidonaldson

Ich bin zwei Jahre zu spät dran, und ja, diese anderen Lösungen funktionieren, aber ich finde es einfacher, all dies in einem Laufblock wie dieser zu handhaben

.run(['$rootScope','$ionicLoading', function ($rootScope,$ionicLoading){


  $rootScope.$on('loading:show', function () {
    $ionicLoading.show({
        template:'Please wait..'
    })
});

$rootScope.$on('loading:hide', function () {
    $ionicLoading.hide();
});

$rootScope.$on('$stateChangeStart', function () {
    console.log('please wait...');
    $rootScope.$broadcast('loading:show');
});

$rootScope.$on('$stateChangeSuccess', function () {
    console.log('done');
    $rootScope.$broadcast('loading:hide');
});

}])

sie brauchen nichts anderes. Ziemlich einfach, huh. Hier ist ein Beispiel davon in Aktion http://plnkr.co/edit/tggNzKlHMc9OwAnBq6eA?p=preview

3
garrettmac

In ui-router sind 1.0 $ stateChange * -Ereignisse veraltet. Verwenden Sie stattdessen Übergangshaken. Weitere Informationen finden Sie im Migrationshandbuch.

https://ui-router.github.io/guide/ng1/migrate-to-1_0#state-change-events

1
Nicholas Glazer

Das Problem mit '$ stateChangeStart' und '$ stateChangeSuccess' ist, dass "$ rootScope.stateIsLoading" nicht aktualisiert wird, wenn Sie zum letzten Status zurückkehren. Gibt es eine Lösung dafür?

    $rootScope.$on('$viewContentLoading',
        function(event){....});

und 

    $rootScope.$on('$viewContentLoaded',
        function(event){....});

aber es gibt das gleiche problem.

0
Hamid Hoseini