在 ui-router 解析期间应用加载微调器 [英] Apply loading spinner during ui-router resolve

查看:27
本文介绍了在 ui-router 解析期间应用加载微调器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

$routeProvider

resolve 属性允许在呈现相应的视图之前执行一些作业.

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

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

我遇到了这篇有趣的帖子,其中包含我上面描述的确切问题:

<块引用>

这种方法会导致糟糕的 UI 体验.用户点击一个按钮来刷新一个列表什么的,整个屏幕得到笼罩在一个通用的微调器中,因为图书馆无法仅针对实际受到影响的视图显示微调器状态变化.不用谢.

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

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

然而,这需要我们启动异步加载和状态同时改变动画.如果我们使用resolve",那么整个异步动画发生在动画开始之前.用户单击,看到一个微调器,然后看到过渡动画.整体状态改变需要~1000ms,太慢了.

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

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

解决方案

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

app.directive('showDuringResolve', function($rootScope) {返回 {链接:函数(范围,元素){element.addClass('ng-hide');var unregister = $rootScope.$on('$routeChangeStart', function() {element.removeClass('ng-hide');});scope.$on('$destroy', unregister);}};});

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

视图 1:

<strong>加载中.</strong>请等候.

视图 2:

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

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

这可以通过创建一个充当回退加载器的指令来解决.它将侦听 $routeChangeStart 并仅在没有以前的路由时才显示加载程序.

一个基本的例子:

app.directive('resolveLoader', function($rootScope, $timeout) {返回 {限制:'E',替换:真的,模板:'<div class="alert alert-success ng-hide"><strong>欢迎!</strong>正在加载内容,请稍候.</div>',链接:函数(范围,元素){$rootScope.$on('$routeChangeStart', function(event, currentRoute, previousRoute) {if (previousRoute) 返回;$超时(功能(){element.removeClass('ng-hide');});});$rootScope.$on('$routeChangeSuccess', function() {element.addClass('ng-hide');});}};});

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

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

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

resolve property of $routeProvider allows to execute some jobs BEFORE corresponding view is rendered.

What if I want to display a spinner while those jobs are executed in order to increase user experience?
Indeed, otherwise the user would feel the application has been blocked since no view elements were displayed for some milliseconds > 600 for instance.

Of course, there was the way to define a global div element out of the current view to display in order to display the spinner thanks to the $scope.$rootChangeStart function.
But I don't want to hide the whole page with just a poor spinner in the middle.
I want some pages of my webapp differ regarding the way the loading is displayed.

I came across this interesting post containing the exact issue I described above:

That approach results in a horrible UI experience. The user clicks on a button to refresh a list or something, and the entire screen gets blanketed in a generic spinner because the library has no way of showing a spinner just for the view(s) that are actually affected by the state change. No thanks.

In any case, after I filed this issue, I realised that the "resolve" feature is an anti-pattern. It waits for all the promises to resolve then animates the state change. This is completely wrong - you want your transition animations between states to run parallel to your data loads, so that the latter can be covered up by the former.

For example, imagine your have a list of items, and clicking on one of them hides the list and shows the item's details in a different view. If we have an async load for the item details that takes, on average, 400ms, then we can cover up the load almost entirely in most cases by having a 300ms "leave" animation on the list view, and a 300ms "enter" animation on the item details view. That way we provide a slicker feel to the UI and can avoid showing a spinner at all in most cases.

However, this requires that we initiate the async load and the state change animation at the same moment. If we use "resolve", then the entire async animation happens before the animation starts. The user clicks, sees a spinner, then sees the transition animation. The whole state change will take ~1000ms, which is too slow.

"Resolve" could be a useful way to cache dependencies between different views if it had the option not to wait on promises, but the current behaviour, of always resolving them before the state change starts makes it almost useless, IMO. It should be avoided for any dependencies that involve async loads.

Should I really stop using resolve to load some data and rather start loading them in the corresponding controller directly? So that I can update the corresponding view as long as the job is executed and in the place I want in the view, not globally.

解决方案

You can use a directive that listens on $routeChangeStart and for example shows the element when it fires:

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

Then you place it on the specific view's loader, for example:

View 1:

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

View 2:

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

The problem with this solution (and many other solutions for that matter) is that if you browse to one of the routes from an external site there will be no previous ng-view template loaded, so your page might just be blank during resolve.

This can be solved by creating a directive that will act as a fallback-loader. It will listen for $routeChangeStart and show a loader only if there is no previous route.

A basic example:

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

The fallback loader would be placed outside the element with ng-view:

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

Demo of it all: http://plnkr.co/edit/7clxvUtuDBKfNmUJdbL3?p=preview

这篇关于在 ui-router 解析期间应用加载微调器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆