【问题标题】:Apply loading spinner during ui-router resolve在 ui-router 解析期间应用加载微调器
【发布时间】:2014-06-13 08:29:37
【问题描述】:

$routeProviderresolve 属性允许在呈现相应视图之前执行一些作业。

如果我想在执行这些作业时显示微调器以增加用户体验怎么办?
实际上,否则用户会觉得应用程序已被阻止,因为例如在 600 毫秒以上的时间内没有显示任何视图元素。

当然,由于$scope.$rootChangeStart 函数,有一种方法可以定义一个全局div 元素在当前视图之外显示以显示微调器。
但我不想隐藏整个页面,中间只有一个糟糕的微调器。
我希望我的 webapp 的某些页面在加载显示方式方面有所不同。

我遇到了this interesting post,其中包含我上面描述的确切问题:

这种方法会导致糟糕的 UI 体验。用户点击 刷新列表或其他内容的按钮,整个屏幕得到 笼罩在一个通用的微调器中,因为图书馆没有办法 仅针对实际受其影响的视图显示微调器 状态变化。不用了。

无论如何,在我提出这个问题后,我意识到“解决” 特征是一种反模式。它等待所有的承诺解决 然后动画状态变化。这是完全错误的——你想要 您的状态之间的过渡动​​画与您的数据并行运行 加载,以便后者可以被前者覆盖。

例如,假设您有一个项目列表,然后单击其中一个 他们隐藏列表并在不同的视图中显示项目的详细信息。 如果我们对项目详细信息进行异步加载,平均而言, 400ms,然后我们可以在大多数情况下几乎完全覆盖负载 在列表视图上有一个 300 毫秒的“离开”动画,以及一个 300 毫秒的“进入”动画 项目详细信息视图上的动画。这样我们就可以提供更光滑的感觉 到 UI 并且在大多数情况下可以避免显示微调器。

但是,这需要我们启动异步加载和状态 同时更改动画。如果我们使用“resolve”,那么 整个异步动画发生在动画开始之前。用户 点击,看到一个微调器,然后看到过渡动画。整体 状态变化大约需要 1000 毫秒,这太慢了。

“解决”可能是一种有用的方式来缓存之间的依赖关系 如果它可以选择不等待承诺,则会有不同的看法,但是 当前的行为,总是在状态改变之前解决它们 开始使它几乎没用,IMO。应该避免任何 涉及异步加载的依赖项。

我真的应该停止使用resolve 来加载一些数据,而是直接在相应的控制器中开始加载它们吗?这样我就可以在作业执行完毕并且在视图中我想要的位置更新相应的视图,而不是全局更新。

【问题讨论】:

  • 我们已经研究了一个类似这个的微调器已经有 2 周的时间了。我们已经尝试了一切。我们使用了“解决”功能,在加载时打开控制器中的微调器,使用“微调器指令”。没有什么能以干净的方式工作。最后,我们有一个混合解决方案,在 rootscope 中的 var 上使用 watch 来激活/停用模态微调器视图。它仍然有一些怪癖,但整体解决方案是可以接受的。如果你想要一个 sn-p,我可以提供给你。
  • @Th0rndike 如果我们直接在控制器中加载数据会怎样?我们将绕过 resolve 属性。代码设计不会像应有的那样干净,但我不知道这会是什么坏方法。事实上,在加载任务时,我们将拥有可供操作的完整视图。
  • 我们的大部分问题是在过渡期间显示微调器。在实例化和填充控制器时加载微调器并不能解决转换视图时的“等待微调器”。如果这不是您的要求,那么您可以接受。
  • 假设有两个页面:A通向B。如果你希望A显示一个“离开”的动画,然后让位置给B,那么我认为目前没有简单的解决方案。但是,如果一旦询问 B,A 就离开(直接),并且 B 通过其控制器显示自己的微调器,那么使用 B 的控制器将很容易而且很好。对于用户而言,A 的控制器是在视图 A 中显示控制器还是在视图 B 中显示 B 的控制器并没有区别。
  • 我们在离开视图时遇到了一些延迟,由于多个数据绑定需要 300 毫秒到 500 毫秒,这就是为什么我们决定使用可以在任何设备上打开/关闭的“全局”微调器我们的观点。

标签: angularjs web-applications angular-ui-router single-page-application


【解决方案1】:

您可以使用监听$routeChangeStart 的指令,例如在元素触发时显示该元素:

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

然后你把它放在特定视图的加载器上,例如:

查看 1:

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

查看 2:

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

此解决方案(以及与此相关的许多其他解决方案)的问题是,如果您从外部站点浏览到其中一个路由,则不会加载以前的 ng-view 模板,因此您的页面可能只是在解决。

这可以通过创建一个充当后备加载器的指令来解决。它将侦听$routeChangeStart 并仅在没有先前路由时显示加载器。

一个基本的例子:

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

后备加载器将被放置在带有 ng-view 的元素之外:

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

全部演示: http://plnkr.co/edit/7clxvUtuDBKfNmUJdbL3?p=preview

【讨论】:

【解决方案2】:

我认为这很整洁

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

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


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

}]);

然后在视图中

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

【讨论】:

  • @Pranay Dytta 你让它尽可能简单
  • 简单但不适用于嵌套的 ui-view。因为全局变量影响所有部分和加载。即使在已加载的部分上也会出现消息。
【解决方案3】:

为了进一步 Pranay 的回答,我就是这样做的。

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>

【讨论】:

  • 在较新版本的 UI Router 中,它是 $stateChangeStart 和 $stateChangeSuccess
【解决方案4】:

我迟到了两年,是的,这些其他解决方案有效,但我发现像这样在一个运行块中处理所有这些更容易

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

}])

您不需要其他任何东西。很容易哈。这是它的example 的实际应用。

【讨论】:

    【解决方案5】:

    在 ui-router 1.0 中,$stateChange* 事件已被弃用。改用转换钩子。有关详细信息,请参阅下面的迁移指南。

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

    【讨论】:

    • 鼓励链接到外部资源,但请在链接周围添加上下文,以便您的其他用户了解它是什么以及为什么存在。始终引用重要链接中最相关的部分,以防目标站点无法访问或永久离线。
    【解决方案6】:

    “$stateChangeStart”和“$stateChangeSuccess”的问题是“$rootScope.stateIsLoading”在您返回上一个状态时不会刷新。 有什么解决办法吗? 我也用过:

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

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

    但是有同样的问题。

    【讨论】:

    • 这并没有提供问题的答案。要批评或要求作者澄清,请在他们的帖子下方发表评论 - 您可以随时对自己的帖子发表评论,一旦您拥有足够的声誉,您就可以对任何帖子发表评论
    猜你喜欢
    • 2014-10-24
    • 1970-01-01
    • 2015-08-05
    • 1970-01-01
    • 1970-01-01
    • 2013-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多