resolve
的$routeProvider
属性允许在呈现相应视图之前执行一些作业。
如果我想在执行这些作业时显示微调器以增加用户体验,该怎么办? 实际上,否则用户会感觉应用程序已被阻止,因为例如没有显示视图元素一些毫秒> 600。
当然,有一种方法可以定义当前视图中的全局div
元素,以便通过$scope.$rootChangeStart
函数显示微调器。
但我不想隐藏整个页面,中间只有一个可怜的微调器。
我希望我的webapp的某些页面在加载显示方式上有所不同。
我遇到了包含我上面描述的确切问题的this interesting post:
这种方法导致可怕的UI体验。用户单击按钮刷新列表或其他内容,整个屏幕在通用微调器中被覆盖,因为库无法仅为实际受状态更改影响的视图显示微调器。不用了,谢谢。
在任何情况下,在我提交此问题后,我意识到“解析”功能是一种反模式。它等待所有承诺解决然后动态状态变化。这是完全错误的 - 您希望状态之间的转换动画与数据加载并行运行,以便前者可以覆盖后者。
例如,假设您有一个项目列表,单击其中一个项目会隐藏列表并在不同视图中显示项目的详细信息。如果我们对项目详细信息的异步加载平均需要400毫秒,那么在大多数情况下,我们可以通过在列表视图上使用300毫秒“离开”动画和300毫秒“输入”动画来几乎完全掩盖负载在项目详细信息视图上。这样我们就可以为UI提供更加流畅的感觉,并且在大多数情况下可以避免显示微调器。
但是,这要求我们在同一时刻启动异步加载和状态更改动画。如果我们使用“resolve”,那么整个异步动画就会在动画开始之前发生。用户单击,查看微调器,然后查看过渡动画。整个状态的变化需要大约1000毫秒,这太慢了。
“Resolve”可以是一种有用的方法来缓存不同视图之间的依赖关系,如果它有选择不等待promises,但当前的行为,总是在状态更改开始之前解析它们使它几乎无用,IMO。对于涉及异步加载的任何依赖项,应该避免使用它。
我真的应该停止使用resolve
加载一些数据,而是直接开始在相应的控制器中加载它们吗?这样我就可以在作业执行时以及在视图中我想要的位置更新相应的视图,而不是全局。
您可以使用侦听$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>
我觉得这很整洁
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的答案,我就是这样做的。
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>
我已经晚了两年了,是的,这些其他的解决方案都有效,但我觉得在一个像这样的运行块中处理所有这些更容易
.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。
在ui-router 1.0中,$ stateChange *事件已被弃用。请改用转换钩子。请参阅下面的迁移指南以获取更多详
https://ui-router.github.io/guide/ng1/migrate-to-1_0#state-change-events
当你回到最后状态时,'$ stateChangeStart'和'$ stateChangeSuccess'的问题是“$ rootScope.stateIsLoading”没有得到刷新。对此有什么解决方案吗?我也用过:
$rootScope.$on('$viewContentLoading',
function(event){....});
和
$rootScope.$on('$viewContentLoaded',
function(event){....});
但是存在同样的问题。