(Angular-ui-router) Pokaż animację ładowania podczas procesu rozwiązywania

80

To jest dwuczęściowe pytanie:

  1. Używam właściwości resolution wewnątrz $ stateProvider.state (), aby pobrać określone dane serwera przed załadowaniem kontrolera. Jak mam się postarać o wyświetlenie animacji ładowania podczas tego procesu?

  2. Mam stany potomne, które również wykorzystują właściwość rozwiązania. Problem polega na tym, że router ui wydaje się chcieć sfinalizować wszystkie rozwiązania przed załadowaniem dowolnego kontrolera. Czy jest jakiś sposób, żebym mógł załadować kontrolery nadrzędne po rozwiązaniu ich decyzji, bez konieczności czekania na wszystkie rozwiązania dziecka? Odpowiedź na to prawdopodobnie rozwiąże również pierwszy problem.

Matt Way
źródło
4
Czy kiedykolwiek rozwiązałeś ten problem?
Stefan Henze,
3
@StefanHenze Nie, po prostu zaakceptowałem to jako błąd architektoniczny ui-routera.
Matt Way,
2
Dyskusja na temat tego konkretnego problemu jest tutaj: github.com/angular-ui/ui-router/issues/456
Johnny Oshika
6
@StefanHenze lol, gra słów nie zamierzona ....
Dave

Odpowiedzi:

71

EDYCJA: Oto jeszcze łatwiejsze rozwiązanie, przetestowane i ładnie działające:

W moim głównym kontrolerze po prostu mam

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

To pokazuje spinner za każdym razem, gdy zbliżamy się do stanu, który ma coś do rozwiązania i ukrywa to, gdy zmiana stanu jest zakończona. Możesz chcieć dodać kilka sprawdzeń w górę hierarchii stanów (tj. Pokazać także spinner, jeśli ładowany stan nadrzędny coś rozwiązuje), ale to rozwiązanie działa dobrze dla mnie.

Oto moja stara sugestia w celach informacyjnych i jako alternatywa:

  1. W kontrolerze aplikacji posłuchaj stateChangeStartzdarzenia i sprawdź, czy masz zamiar przejść do stanu, w którym chcesz wyświetlać pokrętło podczas rozwiązywania (patrz https://github.com/angular-ui/ui-router/wiki/Quick -Reference # 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. Kiedy kontroler w końcu zostanie wywołany, możesz ukryć pokrętło

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

Możesz również sprawdzić, czy nie wystąpiły błędy, które mogły wystąpić podczas rozwiązywania, nasłuchując pliku $stateChangeError zdarzenia i ukrywając animację podczas obsługi błędu.

Nie jest to całkowicie czyste, ponieważ rozdzielasz logikę pokrętła między kontrolerami, ale jest to sposób. Mam nadzieję, że to pomoże.

Stefan Henze
źródło
1
if (toState.resolve)sprawdza, czy konfiguracja stanu ma słownik rozwiązywania. W moim przypadku jest to wystarczająco dobre oszacowanie, że stan wykonuje wywołania usługi asynchronicznej. Zakładam, że jeśli istnieje blok rozstrzygania, to są rozwiązywane zgłoszenia serwisowe. Kod pokazuje i ukrywa spinner tylko wtedy, gdy wykonano wezwanie serwisu. Jak opisałem powyżej, ten kod może wymagać dopracowania, aby sprawdzał również stany nadrzędne, w zależności od przypadku użycia.
Stefan Henze
w „starej sugestii”, do której odwołujesz się $scopew wywołaniu $rootScope. skąd $scopesię tu wzięło?
pdeva
@pdeva, program obsługi zdarzeń został zdefiniowany w jakimś kontrolerze; tam też showSpinnerzostała zdefiniowana funkcja.
Stefan Henze
Nie mogłem użyć, toState.resolvewięc użyłem fromState.name !== toState.name. Poza tym twoja odpowiedź jest fantastyczna, dobra robota!
Jacob Hulse,
22

Opracowałem następujące rozwiązanie, które działa idealnie dla mnie.

1. Dodaj następujący plik app.run

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. Umieść wskaźnik ładowania tuż nad widokiem interfejsu użytkownika. Dodaj id = "ui-view" do div ui-view.

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

3. Dodaj następujące elementy do swojego css

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

UWAGA:

A. Powyższy kod wyświetli wskaźnik ładowania w dwóch przypadkach 1) przy pierwszym załadowaniu aplikacji kątowej 2) przy zmianie widoku.

B. Jeśli nie chcesz, aby wskaźnik był wyświetlany, gdy aplikacja kątowa ładuje się po raz pierwszy (przed załadowaniem jakiegokolwiek widoku), dodaj ukrytą klasę do elementu div ładowania, jak poniżej

<div class="page-loading hidden">Loading...</div>
Blesson Jose
źródło
15
Ten runkod nie jest więc Angular Way. Zamiast manipulować tam dom, zestaw zmiennych zakres, jak loadingVisiblei viewVisible, a następnie w HTML, koniecznie powiedzieć, gdy elementy są widoczne lub ukryte, jak: <div class="page-loading" ng-show="viewVisible">Loading...</div>. Aby opróżnić widok, może jednak wymagać niestandardowej dyrektywy.
Matthias
2
NIE jest to zalecane. Używanie jQuery wewnątrz angularcontext jest nieprzyjemne i tworzy niechcianą zależność między widokiem a kontrolerem - nie jest to dobra praktyka!
Silas Hansen
Podoba mi się to, ale ... Czy to naprawdę jest poprawne w stosunku do DOM?
contributorpw,
Problem z robieniem tego „w sposób kątowy”, jak sugeruje @MatthiasDailey, polega na tym, że musisz powielać logikę pokaż / ukryj w każdym miejscu. Zaletą tego rozwiązania jest to, że możesz mieć globalną metodę pokazywania / ukrywania zawartości lub programów ładujących. Jeśli naprawdę chcesz to zrobić w sposób Angular, utwórz nową dyrektywę, która otacza interfejs użytkownika
thomaux
1
Myślę, że ng-class byłoby lepszym podejściem do tego.
UserX
8

Co powiesz na dodanie treści do div, która zostanie wypełniona przez router ui, gdy właściwości zostaną rozwiązane?

W Twoim index.html

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

Podczas rozwiązywania właściwości użytkownik zobaczy teraz komunikat „Ładowanie ...”. Gdy wszystko będzie gotowe, zawartość zostanie zastąpiona przez router ui z zawartością aplikacji.

Wolfgang
źródło
7
To zadziała, jeśli twoja aplikacja może mieć pełne zastępowanie ładowania aplikacji, ale nie jest wystarczające, jeśli chcesz zbudować „ładowanie ...” hierarchicznie w swoim układzie (np. Wyświetlanie części aplikacji, a ładowanie innej części ... ).
Matt Way
1
Wciąż jest to szybki / brudny / łatwy sposób, aby przejść do ważniejszych rzeczy ... takich jak przyspieszenie decyzji.
Brian Vanderbusch
3
Jeśli ładujesz wiele szablonów do tego widoku, co byłoby idealnym rozwiązaniem. Komunikat „ładowanie…” pojawiłby się tylko po raz pierwszy.
Yasser Shaikh
Czy to możliwe w Ionic? Nie mogłem tego użyć w Ionic.
Anoop.PA,
7

Używam animowanego gifa, który pokazuję tylko wtedy, gdy $httpma oczekujące żądania.

W moim szablonie strony bazowej mam pasek nawigacyjny i kontroler paska nawigacyjnego. Odpowiednia część kontrolera wygląda następująco:

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

Odpowiedni kod w moim html to:

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

Mam nadzieję że to pomogło!

joshvillbrandt
źródło
3
Jak widać na moim pytaniu, nie mam dostępu do kontrolerów, dopóki wszystkie rozwiązania nie zostaną sfinalizowane (co jest problemem)!
Matt Way,
1
Nie powinieneś używać funkcji dla ng-show lub ng-if, ponieważ będą one wywoływane w każdej pętli skrótu. Spowoduje to obniżenie wydajności.
Vikas Gautam
4

Moim pomysłem jest przejście ścieżką na wykresie stanu między przechodzącymi stanami $stateChangeStarti zebranie wszystkich zaangażowanych widoków. Następnie każda ui-viewdyrektywa obserwuje, czy odpowiedni widok jest zaangażowany w przejście i dodaje'ui-resolving' klasę do swojego elementu.

Plunker demo wprowadza dwa stany root: firsta secondten ostatni ma dwie podstanami second.sub1i second.sub2. Państwo second.sub2kieruje się również poglądami footernależącymi do jego dziadków.

aikoven
źródło
3

To jest ładowarka globalna, gdy nawigacja po stronie między dowolnym stanem (dowolna strona), umieszczona w app.js.

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

W html:

<div ng-show="preloader">Loading...</div>
manoz
źródło
Doskonały! Dzięki!
Brynner Ferreira
Nie może być bardziej oczywiste ... może wyjaśnij, jak umieścić coś w widoku interfejsu użytkownika, gdy trwa ładowanie? Zdefiniowanie 2 zmiennych jest tak proste
DDD,
2

Wolę używać dyrektywy, aby dopasować każdą czynność ładowania, głównie w celu utrzymania czystości mojej bazy kodu

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);
      }
    }
  }]);
vorillaz
źródło
2

Użycie $stateChangeStarti tym podobnych zostało od tego czasu przestarzałe i zastąpione przejściami . Tak więc, dla odpowiedzi Stefana Henze, zaktualizowana wersja będzie wyglądać tak:

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

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

Możesz użyć tego w swoim kontrolerze nadrzędnym. Pamiętaj o wstrzyknięciu $transitions-

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

Pamiętaj też, resolveże obiekt będący pustym obiektem będzie nadal renderowany transition.to().resolve == true, więc nie zostawiaj pustego symbolu zastępczego resolvew deklaracji stanu.

IB2DEV
źródło
1

Mój pomysł, aby użyć widoku podczas używania resole w routerze, działa niesamowicie. Spróbuj tego.

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

});
koneri ranjith kumar
źródło
0

Jeśli ktoś używa ngRoute, czeka na resolvezaładowanie następnego widoku i używa angular-bootstrap-uiinterfejsu użytkownika, możesz wykonać następujące czynności :

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;
          }
        ]
      }
    });
  }
]);
Blaskovicz
źródło