(Angular-UI-Router) Zeigt die Ladeanimation während des Auflösungsprozesses an


80

Dies ist eine zweiteilige Frage:

  1. Ich verwende die Auflösungseigenschaft in $ stateProvider.state (), um bestimmte Serverdaten abzurufen, bevor der Controller geladen wird. Wie würde ich vorgehen, um eine Ladeanimation während dieses Vorgangs anzuzeigen?

  2. Ich habe untergeordnete Zustände, die auch die Auflösungseigenschaft verwenden. Das Problem ist, dass der UI-Router anscheinend alle Auflösungen abschließen möchte, bevor ein Controller geladen wird. Gibt es eine Möglichkeit, die übergeordneten Controller zum Laden zu bringen, sobald ihre Auflösungen aufgelöst wurden, ohne auf alle untergeordneten Auflösungen warten zu müssen? Eine Antwort darauf wird wahrscheinlich auch das erste Problem lösen.


4
Haben Sie dieses Problem jemals behoben?
Stefan Henze

3
@StefanHenze Nein, ich habe dies nur als Architekturfehler des UI-Routers akzeptiert.
Matt Way

2
Es gibt eine Diskussion über genau dieses Problem hier: github.com/angular-ui/ui-router/issues/456
Johnny Oshika

6
@StefanHenze lol, kein Wortspiel beabsichtigt ....
Dave

Antworten:


71

EDIT: Hier ist eine noch einfachere Lösung, getestet und funktioniert gut:

In meinem Hauptcontroller habe ich einfach

$scope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
    if (toState.resolve) {
        $scope.showSpinner();
    }
});
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
    if (toState.resolve) {
        $scope.hideSpinner();
    }
});

Dies zeigt den Spinner, wann immer wir in einen Zustand gehen, der etwas zu lösen hat, und verbirgt ihn, wenn die Zustandsänderung abgeschlossen ist. Möglicherweise möchten Sie die Statushierarchie überprüfen (dh auch den Spinner anzeigen, wenn ein überladener übergeordneter Status etwas auflöst), aber diese Lösung funktioniert für mich einwandfrei.

Hier ist mein alter Vorschlag als Referenz und als Alternative:

  1. Hören stateChangeStartSie sich in Ihrem Anwendungscontroller das Ereignis an und prüfen Sie, ob Sie während der Auflösung in einen Zustand wechseln möchten, in dem Sie einen Spinner anzeigen möchten (siehe https://github.com/angular-ui/ui-router/wiki/Quick) -Referenz # wiki-events-1 )

    $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
        if (toState.name == 'state.with.resolve') {
            $scope.showSpinner();  //this is a function you created to show the loading animation
        }
    })
    
  2. Wenn Ihr Controller endlich aufgerufen wird, können Sie den Spinner ausblenden

    .controller('StateWithResolveCtrl', function($scope) {
        $scope.hideSpinner();
    })
    

Möglicherweise möchten Sie auch nach Fehlern suchen, die während der Lösung aufgetreten sind, indem Sie das $stateChangeErrorEreignis abhören und die Animation ausblenden, während Sie den Fehler behandeln.

Dies ist nicht ganz sauber, da Sie die Logik für den Spinner auf die Controller verteilen, aber es ist ein Weg. Ich hoffe es hilft.


1
if (toState.resolve)Überprüft, ob die Statuskonfiguration ein Auflösungswörterbuch enthält. In meinem Fall ist dies eine ausreichend gute Schätzung, dass der Status asynchrone Dienstaufrufe ausführt. Ich gehe davon aus, dass bei einem Auflösungsblock Serviceaufrufe aufgelöst werden. Der Code zeigt und versteckt den Spinner nur, wenn Serviceanrufe getätigt wurden. Wie oben beschrieben, muss dieser Code je nach Anwendungsfall möglicherweise verfeinert werden, um auch die übergeordneten Status zu überprüfen.
Stefan Henze

im 'alten vorschlag' verweisen sie $scopein einem anruf auf $rootScope. wo kommt das $scopevon hier her
Pdeva

@pdeva, der Ereignishandler wurde in einem Controller definiert. Dort wurde auch die showSpinnerFunktion definiert.
Stefan Henze

Ich konnte es nicht benutzen, toState.resolvealso habe ich es benutzt fromState.name !== toState.name. Abgesehen davon ist Ihre Antwort fantastisch, gut gemacht!
Jacob Hulse

22

Ich habe die folgende Lösung entwickelt, die perfekt für mich funktioniert.

1. Fügen Sie die folgende app.run hinzu

app.run(function($rootScope){

    $rootScope
        .$on('$stateChangeStart', 
            function(event, toState, toParams, fromState, fromParams){ 
                $("#ui-view").html("");
                $(".page-loading").removeClass("hidden");
        });

    $rootScope
        .$on('$stateChangeSuccess',
            function(event, toState, toParams, fromState, fromParams){ 
                $(".page-loading").addClass("hidden");
        });

});

2. Platzieren Sie die Ladeanzeige direkt über der UI-Ansicht. Fügen Sie id = "ui-view" zu ui-view div hinzu.

<div class="page-loading">Loading...</div>
<div ui-view id="ui-view"></div>

3. Fügen Sie Ihrem CSS Folgendes hinzu

.hidden {
  display: none !important;
  visibility: hidden !important;
}

HINWEIS:

A. Der obige Code zeigt in zwei Fällen eine Ladeanzeige an: 1) wenn die Winkel-App zum ersten Mal geladen wird 2) wenn die Ansicht geändert wird.

B. Wenn Sie nicht möchten, dass der Indikator beim ersten Laden der Winkel-App angezeigt wird (bevor eine Ansicht geladen wird), fügen Sie dem Ladediv wie unten eine versteckte Klasse hinzu

<div class="page-loading hidden">Loading...</div>

15
Dieser runCode ist soo nicht der Angular Way. Anstatt das DOM dort zu manipulieren, setzen Sie Bereichsvariablen wie loadingVisibleund viewVisibleund dann im HTML-Code unbedingt, wann die Elemente sichtbar oder ausgeblendet sind, wie : <div class="page-loading" ng-show="viewVisible">Loading...</div>. Zum Leeren der Ansicht ist jedoch möglicherweise eine benutzerdefinierte Anweisung erforderlich.
Matthias

2
Dies wird NICHT empfohlen. Die Verwendung von jQuery im Winkelkontext ist unangenehm und führt zu einer unerwünschten Abhängigkeit zwischen Ihrer Ansicht und dem Controller - keine gute Vorgehensweise!
Silas Hansen

Ich mag das aber ... Ist es wirklich richtig in Bezug auf das DOM?
Contributorpw

Das Problem dabei, wie @MatthiasDailey vorschlägt, ist, dass Sie die Ein- / Ausblendlogik überall duplizieren müssen. Das Schöne an dieser Lösung ist, dass Sie eine globale Methode haben können, um den Inhalt oder die Lader ein- / auszublenden. Wenn Sie dies wirklich auf Angular-Weise tun möchten,
erstellen Sie

1
Ich denke, ng-class wäre ein besserer Ansatz dafür.
UserX


8

Wie wäre es mit dem Hinzufügen von Inhalten zum div, die vom UI-Router ausgefüllt werden, sobald die Eigenschaften aufgelöst sind?

In deiner index.html

<div ui-view class="container">
    Loading....
</div>

Der Benutzer sieht nun "Laden ...", während die Eigenschaften aufgelöst werden. Sobald alles fertig ist, wird der Inhalt durch den UI-Router durch den Inhalt Ihrer Apps ersetzt.


7
Dies würde funktionieren, wenn Ihre Anwendung einen vollständigen Ersatz für das Laden von Apps haben kann, aber nicht, wenn Sie "Laden ..." hierarchisch in Ihr Layout einbauen möchten (z. B. wenn ein Teil Ihrer Anwendung angezeigt wird und ein anderer Teil das Laden anzeigt ... ).
Matt Way

1
Es ist immer noch eine schnelle / schmutzige / einfache Möglichkeit, Sie zu wichtigeren Dingen zu bewegen ... wie zum Beispiel Ihre Vorsätze zu beschleunigen.
Brian Vanderbusch

3
Wenn Sie mehrere Vorlagen in diese Ansicht laden, möchten Sie dies im Idealfall. Die Meldung "Laden ...." wird nur zum ersten Mal angezeigt.
Yasser Shaikh

Ist das in Ionic möglich? Ich konnte diesen Weg in Ionic nicht verwenden.
Anoop.PA

7

Ich verwende ein animiertes GIF, das nur angezeigt wird, wenn $httpausstehende Anforderungen vorliegen.

In meiner Basisseitenvorlage habe ich eine Navigationsleiste und einen Navigationsleisten-Controller. Der relevante Teil des Controllers sieht folgendermaßen aus:

controllers.controller('NavbarCtrl', ['$scope', '$http',
    function ($scope, $http) {
        $scope.hasPendingRequests = function () {
            return $http.pendingRequests.length > 0;
        };
    }]);

Der entsprechende Code in meinem HTML lautet:

<span class="navbar-spinner" ng-show="hasPendingRequests()">
    <img src="/static/img/spinner.gif">
</span>

Ich hoffe das hilft!


3
Wie Sie meiner Frage entnehmen können, habe ich keinen Zugriff auf Controller, bis alle Lösungen abgeschlossen sind (was das Problem ist)!
Matt Way

1
Sie sollten die Funktion nicht für ng-show oder ng-if verwenden, da sie in jeder Digest-Schleife aufgerufen werden. Dies verringert die Leistung.
Vikas Gautam

4

Meine Idee ist es, den Pfad im Zustandsdiagramm zwischen Übergangszuständen zu gehen $stateChangeStartund alle beteiligten Ansichten zu sammeln. Dann ui-viewüberprüft jede Direktive, ob die entsprechende Ansicht am Übergang beteiligt ist, und fügt 'ui-resolving'ihrem Element eine Klasse hinzu .

Die Plunker- Demo führt zwei Root-Zustände ein: firstund secondletzterer hat zwei Unterzustände second.sub1und second.sub2. Der Staat second.sub2zielt auch auf footerAnsichten ab, die seinen Großeltern gehören.


3

Dies ist ein Loader für global, wenn die Seite zwischen einem beliebigen Status (einer beliebigen Seite) in app.js navigiert

.run(
    ['$rootScope',
        function($rootScope) {
            $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
                $rootScope.preloader = true;
            })
            $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
                $rootScope.preloader = false;
            })
        }
    ])

In HTML:

<div ng-show="preloader">Loading...</div>

Perfekt! Vielen Dank!
Brynner Ferreira

Könnte nicht offensichtlicher sein ... erklären Sie vielleicht, wie Sie etwas in die UI-Ansicht einfügen, während das Laden läuft? Das Definieren von 2 Variablen ist so trivial
DDD

2

Ich bevorzuge die Verwendung einer Direktive, um alle Ladeaktivitäten abzugleichen, hauptsächlich, um meine Codebasis sauber zu halten

angular.module('$utilityElements', [])
.directive('loader',['$timeout','$rootScope', function($timeout, $rootScope) {
    return {
      restrict: 'A',
      template: '<div id="oneloader" class="hiddenload">loading...</div>',
      replace: true,
      compile: function (scope, element, attrs) {
        $timeout(function(){
          $rootScope
              .$on('$stateChangeStart',
                  function(event, toState, toParams, fromState, fromParams){
                      $("#oneloader").removeClass("hiddenload");
              });

          $rootScope
              .$on('$stateChangeSuccess',
                  function(event, toState, toParams, fromState, fromParams){
                      //add a little delay
                      $timeout(function(){
                        $("#oneloader").addClass("hiddenload");
                      },500)
              });
        }, 0);
      }
    }
  }]);

2

Die Verwendung von $stateChangeStartund dergleichen wurde inzwischen veraltet und durch Übergangshaken ersetzt . Für die Antwort von Stefan Henze wäre die aktualisierte Version also:

$transitions.onStart({}, function(transition) {
  if (transition.to().resolve) {
    $scope.showSpinner();
  }
});

$transitions.onSuccess({}, function(transition) {
  if (transition.to().resolve) {
    $scope.hideSpinner();
  }
});

Sie können dies in Ihrem übergeordneten Controller verwenden. Denken Sie daran zu injizieren $transitions-

.controller('parentController',['$transitions',function($transitions){...}]);

Beachten Sie außerdem, resolvedass ein leeres Objekt weiterhin gerendert transition.to().resolve == truewird. Lassen Sie also keinen leeren Platzhalter resolvein der Statusdeklaration.


1

Meine Idee, die Ansicht zu verwenden, während Resole im Router verwendet wird, funktioniert hervorragend. Versuche dies.

//edit index.html file 
<ion-nav-view>
    <div ng-show="loadder" class="loddingSvg">
        <div class="svgImage"></div>
    </div>
</ion-nav-view>

// css file

.loddingSvg {
    height: 100%;
    background-color: rgba(0, 0, 0, 0.1);
    position: absolute;
    z-index: 99;
    left: 0;
    right: 0;
}

.svgImage {
    background: url(../img/default.svg) no-repeat;
    position: relative;
    z-index: 99;
    height: 65px;
    width: 65px;
    background-size: 56px;
    top: 50%;
    margin: 0 auto;
}

// edit app.js

 .run(function($ionicPush, $rootScope, $ionicPlatform) {




        $rootScope.$on('$stateChangeStart',
            function(event, toState, toParams, fromState, fromParams) {
                $rootScope.loadder = true;
            });

        $rootScope.$on('$stateChangeSuccess',
            function(event, toState, toParams, fromState, fromParams) {
                $rootScope.loadder = false;
            });

});

0

Wenn jemand verwendet ngRoute, wartet, resolvebevor er die nächste Ansicht lädt, und angular-bootstrap-uifür ui verwendet, können Sie Folgendes tun :

app.config([
  "$routeProvider", "$locationProvider", function($routeProvider, $locationProvider) {
    return $routeProvider.when("/seasons/:seasonId", {
      templateUrl: "season-manage.html",
      controller: "SeasonManageController",
      resolve: {
        season: [
          "$route", "$q", "$http", "$modal", function($route, $q, $http, $modal) {
            var modal, promise, seasonId;
            modal = $modal.open({
              backdrop: "static",
              template: "<div>\n  <div class=\"modal-header\">\n    <h3 class=\"modal-title\">\n      Loading...\n    </h3>\n  </div>\n  <div class=\"modal-body\">\n    <progressbar\n      class=\"progress-striped active\"\n      value=\"'100'\">\n    </progressbar>\n  </div>\n</div>",
              keyboard: false,
              size: "lg"
            });
            promise = $q.defer();
            seasonId = $route.current.params.seasonId;
            $http.get("/api/match/seasons/" + seasonId).success(function(data) {
              modal.close();
              promise.resolve(data);
            }).error(function(data) {
              modal.close();
              promise.reject(data);
            });

            return promise.promise;
          }
        ]
      }
    });
  }
]);
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.