在Angular(以及所有SPA JS框架)中,假设页面导航非常快速且对用户“无缝”。这种速度的唯一瓶颈是使用API调用从我们的服务器检索数据。因此,在我们等待我们的API调用获得响应的同时,找到一个我们可以呈现整个页面的解决方案似乎是合理的,除了所呈现的物理数据之外。
我是Angular的新手,我遇到了一些阻碍我实现上述目标和功能的问题。让我带您了解我在学习Angular时遇到的两个问题
问题1:对于Angular
,API响应太慢起初,我只是在控制器内调用我的API。我有多个数据表,在收到我的回复后会填充。在得到响应之前,我的表有一个简单的<tr>
,其中包含一个表示数据加载的.gif“spinner”。我的$scope.gotAPIResponse
属性最初设置为false
,然后在API响应后,它设置为true
。
我使用这个布尔值来显示和隐藏“微调器”和表数据。这会导致HTML成功呈现,并且一切对用户来说都很好。但是,仔细查看JavaScript控制台,您可以看到Angular引发了许多错误,因为它在API调用得到响应之前尝试$compile
我的模板。错误绝不是一件好事,所以我不得不即兴发挥。
在'LandingController'中
function init() {
$scope.gotAPIResponse = false;
// Get Backup data
API.Backups.getAll(function (data) {
console.log(data);
$scope.ActiveCurrentPage = 0;
$scope.InactiveCurrentPage = 0;
$scope.pageSize = 25;
$scope.data = data.result;
$scope.gotAPIResponse = true;
});
}
以下是抛出错误的HTML模板..本机模板和分页指令都会引发错误
<tbody>
<tr ng-hide="gotAPIResponse"><td colspan="6"><p class="spinner"></p></td></tr>
<tr ng-if="gotAPIResponse" ng-repeat="active in data.ActiveBackups | paginate:ActiveCurrentPage*pageSize | limitTo:pageSize">
<td><a ui-sref="order.overview({ orderId: active.ServiceId })" ng-click="select(active)">{{ active.Customer }}</a></td>
<td>{{ active.LastArchived }}</td>
<td>{{ active.ArchiveSizeGB | number:2 }}</td>
<td>{{ active.NumMailboxes }}</td>
<td>{{ active.ArchivedItems }}</td>
<td><input type="button" class="backup-restore-btn" value="Restore" /></td>
</tr>
</tbody>
</table>
<div pagination backup-type="active"></div>
问题2:使用resolve属性阻止所有内容
然后我查看了resolve
(ui-router)和$stateProvider
(基本角度)中可用的$routeProvider
属性。这允许我推迟Controller的实例化,直到收到我的API调用$promise
为止。
它的工作原理应该如何,我不再收到任何错误,因为在呈现HTML模板之前API响应可用。但问题是,当您尝试转到该状态或路线时,整个视图将暂时不可用,直到收到$promise
为止。这似乎会“滞后”或“阻塞”给用户(看起来很糟糕)
使用resolve属性
$stateProvider
.state('dashboard', {
url: '/',
controller: 'LandingController',
resolve: {
res: ['API', function(API){
return API.Backups.getAll(function (data) {
return data.result;
}).$promise;
}]
}
})
然后这个控制器在API调用之后被实例化,但是它之间的时间就像1-2秒......太慢了!
app.controller('LandingController', ['$scope', 'API', 'res', function ($scope, API, res) {
$scope.data = res.result;
...
解决方案:正好在
之间为了解决这个问题,我们需要一个正在我已经尝试过的解决方案之间的解决方案。我们需要尽可能快地实例化控制器,因此可以设置$scope.gotAPIResponse
,从而导致“微调器”和DOM的其余部分显示,但是,我们需要推迟HTML模板和其他指令的特定部分这样就不会抛出错误。
人们在生产中使用这个问题是否有效解决方案?
答案 0 :(得分:4)
最后,这最终成为一个更简单的解决方案。经过一番讨论后,我们稍微重构了一下视图,以避免ng-repeat
在ng-if
之前执行而不是所需的行为。
所以不要这样:
<tbody>
<tr ng-if="!gotAPIResponse">
<!-- show spinner -->
</tr>
<tr ng-if="gotAPIResponse" ng-repeat="stuf in stuffs">
<!-- lots o' DOM -->
</tr>
</tbody>
我们将ng-if
提升了一级:
<tbody ng-if="!gotAPIResponse">
<tr >
<!-- show spinner -->
</tr>
</tbody>
<tbody ng-if="gotAPIResponse">
<tr ng-repeat="stuf in stuffs">
<!-- lots o' DOM -->
</tr>
</tbody>
这样,每行ng-if
都不会被复制,并且会停止进一步处理,直到条件满足为止。
通过使用指令并在$rootScope
我们的想法是在可用于显示/隐藏某个元素的范围上切换属性。在这种情况下,一个简单的加载......&#34;文本,但在您的情况下,它可以很容易地用于显示微调器。
var routeChangeSpinner = function($rootScope){
return {
restrict:'E',
template:"<h1 ng-if='isLoading'>Loading...</h1>",
link:function(scope, elem, attrs){
scope.isLoading = false;
$rootScope.$on('$routeChangeStart', function(){
scope.isLoading = true;
});
$rootScope.$on('$routeChangeSuccess', function(){
scope.isLoading = false;
});
}
};
};
routeChangeSpinner.$inject = ['$rootScope'];
app.directive('routeChangeSpinner', routeChangeSpinner);
然后在您的HTML中,您只需执行此操作:
<div class="row">
<!-- I will show up while a route is changing -->
<route-change-spinner></route-change-spinner>
<!-- I will be hidden as long as a route is loading -->
<div ng-if="!isLoading" class="col-lg-12" ng-view=""></div>
</div>
瞧!在更改路线时显示加载指示器的简单方法。
答案 1 :(得分:0)
这是你可以做的(下面的例子):
以下是我提供的示例的解释:
这里是plnkr showing directive update with $http
// index.html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.2.x" src="https://code.angularjs.org/1.2.20/angular.js" data-semver="1.2.20"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<input type="button" ng-click="changedata()" value="Change"/>
<div myview data="model.data"></div>
</body>
</html>
// app.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $http) {
$scope.name = 'World';
$scope.model = {
data: ["1", "2"]
};
$scope.changedata = function() {
// updating data explicitly
$scope.model.data = [4, 5, 6];
url = "http://api.openweathermap.org/data/2.5/weather"
return $http.get(url, {
params: {
q: "New York, NY"
}
}).then(function(result) {
console.log("result=");
console.log(result);
if (result !== null) {
$scope.model.data.push(result.data.weather[0].description);
return result.data.weather[0].description;
}
return "Weather not found";
});
};
// updating data implicitly
$scope.changedata();
});
app.directive("myview", function() {
return {
restrict: 'EA',
template: '<table><tr ng-repeat="d in data"><td>{{d}}</td></tr></table>',
scope: {
data: '='
}
};
});
答案 2 :(得分:0)
模板呈现时遇到错误的第一个问题是因为data
在promise解析之后才存在。在启动API调用之前,一个简单的$scope.data = { ActiveBackups:null }
应该可以解决这个问题。当angular评估ng-repeat
表达式active in data.ActiveBackups
时,它将不再尝试引用未定义的属性data
。
如果您有更长的加载请求,那么您可以通过不使用结算来更好地控制UX。否则,解析倾向于使更简单的控制器代码。