web-dev-qa-db-fra.com

Appliquer le spinner de chargement lors de la résolution du routeur ui

La propriété resolve de $routeProvider permet d'exécuter certains travaux AVANT que la vue correspondante soit rendue. 

Que se passe-t-il si je souhaite afficher un compteur pendant l'exécution de ces travaux afin d'améliorer l'expérience utilisateur?
En effet, sinon, l'utilisateur aurait l'impression que l'application a été bloquée car aucun élément de vue n'a été affiché pendant quelques millisecondes> 600 par exemple.

Bien sûr, il y avait moyen de définir un élément div global hors de la vue actuelle à afficher afin d'afficher le spinner grâce à la fonction $scope.$rootChangeStart.
Mais je ne veux pas cacher la page entière avec juste un mauvais disque au milieu.
Je veux que certaines pages de mon application Web diffèrent par la façon dont le chargement est affiché.

Je suis tombé sur cet article intéressant contenant le problème exact que j'ai décrit ci-dessus:

Cette approche donne lieu à une expérience d’assurance-chômage horrible. L'utilisateur clique sur un bouton pour actualiser une liste ou quelque chose, et l'écran entier obtient recouvert dans un spinner générique parce que la bibliothèque n'a aucun moyen de montrant une image juste pour la ou les vues réellement affectées par le changement d'état. Non merci.

Dans tous les cas, après avoir classé ce problème, je me suis rendu compte que la "résolution" fonctionnalité est un anti-motif. Il attend toutes les promesses pour résoudre anime ensuite le changement d'état. C'est complètement faux - vous voulez vos animations de transition entre les états se déroulent parallèlement à vos données des charges, de sorte que ces dernières puissent être recouvertes par les premières.

Par exemple, imaginez que vous ayez une liste d’éléments et que vous cliquiez sur l’un des fichiers elles masquent la liste et affichent les détails de l'élément dans une vue différente . Si nous avons une charge asynchrone pour les détails de l'élément, cela prend en moyenne, 400 ms, nous pouvons alors couvrir la charge presque entièrement dans la plupart des cas par avoir une animation "laisser" de 300 ms dans la vue liste et une entrée "entrer" de 300 ms animation sur la vue de détail de l'article. De cette façon, nous donnons une sensation de glisse l’interface utilisateur et peut éviter de montrer un fileur du tout dans la plupart des cas.

Cependant, cela nécessite que nous lancions la charge async et l'état changer d'animation au même moment. Si nous utilisons "résoudre", alors le toute l'animation asynchrone se produit avant le début de l'animation. L'utilisateur clique, voit un spinner, puis voit l’animation de transition. La totalité le changement d'état prendra ~ 1000ms, ce qui est trop lent.

"Résoudre" pourrait être un moyen utile de mettre en cache les dépendances entre différents points de vue s’il avait la possibilité de ne pas attendre les promesses, mais le comportement actuel, de toujours les résoudre avant le changement d'état commence rend presque inutile, IMO. Il devrait être évité pour tout dépendances impliquant des charges asynchrones.

Devrais-je vraiment arrêter d'utiliser resolve pour charger des données et commencer plutôt à les charger directement dans le contrôleur correspondant? Pour que je puisse mettre à jour la vue correspondante tant que le travail est exécuté et à la place que je veux dans la vue, pas globalement.

35
Mik378

Vous pouvez utiliser une directive qui écoute sur $routeChangeStart et montre par exemple l'élément lorsqu'il se déclenche:

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

Vous le placez ensuite sur le chargeur de la vue spécifique, par exemple:

Vue 1:

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

Vue 2:

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

Le problème de cette solution (et de nombreuses autres solutions à cet égard) est que si vous naviguez vers l’un des itinéraires à partir d’un site externe, aucun modèle précédent ng-view ne sera chargé, de sorte que votre page risque d’être vide lors de la résolution.

Cela peut être résolu en créant une directive qui agira comme un chargeur de secours. Il écoutera $routeChangeStart et affichera un chargeur uniquement s’il n’ya pas d’itinéraire précédent.

Un exemple de base:

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

Le chargeur de secours serait placé à l'extérieur de l'élément avec ng-view:

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

Démo de tout cela:http://plnkr.co/edit/7clxvUtuDBKfNmUJdbL3?p=preview

50
tasseKATT

je pense que c'est assez chouette

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

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


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

}]);

et puis sur la vue

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

Pour approfondir la réponse de Pranay, voici comment je l'ai fait.

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

Je suis deux ans en retard par rapport à cela, et oui, ces autres solutions fonctionnent, mais je trouve plus facile de gérer tout cela dans un bloc d'exécution comme celui-ci.

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

}])

vous n'avez besoin de rien d'autre. Assez facile hein. en voici un exemple en action http://plnkr.co/edit/tggNzKlHMc9OwAnBq6eA?p=preview

3
garrettmac

Dans ui-router 1.0, les événements $ stateChange * sont obsolètes. Utilisez plutôt un crochet de transition. Voir le guide de migration ci-dessous pour plus de détails.

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

1
Nicholas Glazer

Le problème avec '$ stateChangeStart' et '$ stateChangeSuccess' est "$ rootScope.stateIsLoading" n'est pas actualisé lorsque vous revenez au dernier état.

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

et 

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

mais il y a le même problème.

0
Hamid Hoseini