Angularjs选项卡在渲染时加载微调器

时间:2015-06-19 11:08:37

标签: javascript jquery angularjs

我有一个带有一些标签的页面,每个标签都有大量的angularjs绑定。This是我面临问题的示例页面。每个标签大约需要10秒才能呈现。

所以我计划在制表符渲染时给出一个加载微调器。 因此,我计划在单击选项卡时显示加载微调器,并在ng-repeat的末尾($last)删除微调器。

在ng-click on标签上,我激活了旋转装载机

<ul>
    <li ng-repeat="tab in tabs" 
        ng-class="{active:isActiveTab(tab.url)}" 
        ng-click="onClickTab(tab)">{{tab.title}}
   </li>
</ul>

在控制器中

$scope.onClickTab = function (tab) {
     showLoader();
     $scope.currentTab = tab.url;
 }

要检查ng-repeat是否已完成,我使用了以下指令

.directive('onFinishRender', function ($timeout) {    
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {

                $timeout(function () {
                    scope.$emit('ngRepeatFinished');
                });
            }
        }
    }
});

$scope.$on('ngRepeatFinished', function (ngRepeatFinishedEvent) {
        removeLoader();
});

showLoader和removeLoader是一个简单的函数,它附加并删除了一个带有简单加载微调器的div。

function showLoader() {
    $('body').append('<div class="loader"></div>');
}

function removeLoader() {
    $('.loader').fadeOut(function () {
        $(this).remove();
    });
}

预期结果:旋转装载器在单击选项卡时显示,直到ng-repeat完成为止(即点击的选项卡完全呈现)

实际结果:单击选项卡时未显示加载程序,它几乎出现在ng-repaet的末尾,并显示一小段时间。 Here你可以观察到上述行为。我认为由于角度绑定过程导致页面冻结,页面无法显示微调器。

有人可以帮我解决这个问题吗?

5 个答案:

答案 0 :(得分:14)

您可以像这样更改代码:

$timeout(function() {
  $scope.currentTab = tab.url 
}, 100);

演示:http://jsfiddle.net/eRGT8/1053/

我做的是,我将currentTab更改为下一个摘要周期。 (这是一种黑客,而不是我引以为傲的解决方案。)因此,在第一个摘要周期(调用$timeout之前),只显示加载。在下一个摘要周期中,阻塞ng-repeat内容开始工作,但由于我们之前显示加载,因此显示。

:)

这解决了您的问题,但是长时间运行并阻止完全挂起浏览器的javascript并不是一个好的用户体验。此外,由于浏览器完全挂起,加载的gif不会动画,只会显示。

答案 1 :(得分:6)

我强烈建议首先阅读这篇SO线程,由AngularJS本人的作者撰写,解决根本问题。

Delaying AngularJS route change until model loaded to prevent flicker

Misko要求通过建议使用$ routeProvider回答他自己关于这个主题的问题,如下所示:

 $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html', 
        controller: PhoneListCtrl, 
        resolve: PhoneListCtrl.resolve}). // <-- this is the connection when resolved
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html', 
        controller: PhoneDetailCtrl, 
        resolve: PhoneDetailCtrl.resolve}).
      otherwise({redirectTo: '/phones'});

以下是您正在寻找的成品/解决方案,这是AngularJS手机演示的增强版:

http://mhevery.github.io/angular-phonecat/app/#/phones

这需要NgRoute(),但这是正确的方法。在这里使用$ timeout对我来说似乎有些惹人注意 - 我不是说你需要ngRoute然而我不知道如何在没有它的情况下将它拉下来(我知道你的标签目前可能没有路由,但我希望它无论如何都有帮助)

此外,也可以使用angular.element(document).ready(function(){})找到;对于你的初始有效载荷也将解决你的问题。

angular.element(document).ready(function(){
  $('.loading').remove(); // Just an example dont modify the dom outside of a directive!
  alert('Loaded!');
});

你可以看到没有$ timeout(function(){});使用任何一种方法,这里是angular.element.ready()的证明工作 - 没有路由器:

工作演示 http://codepen.io/nicholasabrams/pen/MwOMNR

*如果你真的需要我,我可以制作一些标签,但你没有在你的帖子中提供一个有效的代码示例,所以我使用了另一个我为另一个线程制作的演示来展示解决方案。

这里是另一种解决方案,这次我用你的例子了!

<强> http://jsfiddle.net/eRGT8/1069/

这次我在$ index === $ last(ngRepeat()中的最后一个元素)时触发一个函数,这样装载器从dom中移除或者当重复完成其业务时隐藏起来)

答案 2 :(得分:1)

我通常使用ng-if在我的HTML中解决这个问题。

在你的情况下,这将变成:

<ul>
  <span ng-if="!tabs">Content loading...</span>
  <span ng-if="tabs && tabs.length < 1">No tabs available</span>
  <span ng-if="tabs && tabs.length > 0">
    <li ng-repeat="tab in tabs" 
        ng-class="{active:isActiveTab(tab.url)}" 
        ng-click="onClickTab(tab)">{{tab.title}}
   </li>
  </span>
</ul>

答案 3 :(得分:1)

我认为在重新呈现页面时显示加载微调器不具有意义。渲染引擎是单线程的。除了网络操作之外,几乎所有事情都发生在一个线程中。在Firefox和Safari中,这是浏览器的主线程。在chrome中,它是制表符进程的主线程。 网络操作可以由几个并行线程执行。

您可以阅读here

只有一种方法可以解决这个问题 - 重新设计页面:仅显示部分数据,延迟加载等。

答案 4 :(得分:0)

你可以为每个li示例加载计数器创建自定义指令,然后添加将保存计数器的依赖服务,并在链接函数内的每个指令中调用

&#13;
&#13;
var app = angular.module('app', []);

app.controller('List', function(LoadingCounter) {

  this.tabs = [{
    title: "Test tab",
    url: "http://google.sk"
  }, {
    title: "Test tab",
    url: "http://google.sk"
  }, {
    title: "Test tab",
    url: "http://google.sk"
  }]

  this.LoadingCounter = LoadingCounter;

});

app.directive('spinningLoader', function(LoadingCounter) {
  return {
    restrict: 'A',
    scope: {
      max: "="
    },
    link: function(scope) {
      LoadingCounter.updateCounter(scope.max);
    }
  }
});

app.factory('LoadingCounter', function($timeout) {
  var service = {};

  service.currentCounter = 0;
  service.finished = false;
  service.updateCounter = function(max) {
    service.currentCounter++;
    if (service.currentCounter == max) {
      //example timeout
      $timeout(function() {
        service.finished = true;
      }, 2000);

    }
  }
  service.isFinished = function() {
    return service.finished;

  }
  return service;
});
&#13;
.hidden {
  display: none;
}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<body ng-app="app" ng-controller="List as _">
  <div ng-if="!_.LoadingCounter.isFinished()">Loading</div>
  <ul ng-class="{'hidden' : !_.LoadingCounter.isFinished() }">
    <li spinning-loader max="_.tabs.length" ng-repeat="tab in _.tabs" ng-class="{active:isActiveTab(tab.url)}" ng-click="onClickTab(tab)">{{tab.title}}
    </li>
  </ul>
</body>
&#13;
&#13;
&#13;