我想知道是否有一种方法(类似于Gmail)让AngularJS延迟显示一个新的路由,直到每个模型和它的数据已经使用各自的服务获取。
例如,如果有一个ProjectsController列出了所有的项目,而project_index.html是显示这些项目的模板,那么Project.query()将在显示新页面之前被完全获取。
在此之前,旧页面仍将继续显示(例如,如果我正在浏览另一个页面,然后决定查看这个Project索引)。
我想知道是否有一种方法(类似于Gmail)让AngularJS延迟显示一个新的路由,直到每个模型和它的数据已经使用各自的服务获取。
例如,如果有一个ProjectsController列出了所有的项目,而project_index.html是显示这些项目的模板,那么Project.query()将在显示新页面之前被完全获取。
在此之前,旧页面仍将继续显示(例如,如果我正在浏览另一个页面,然后决定查看这个Project索引)。
当前回答
我从Misko的代码上面工作,这是我所做的。这是一个更当前的解决方案,因为$defer已经更改为$timeout。然而,替换$timeout将等待超时时间(在Misko的代码中,1秒),然后返回数据,希望它能及时解决。用这种方法,它会尽快返回。
function PhoneListCtrl($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}
PhoneListCtrl.resolve = {
phones: function($q, Phone) {
var deferred = $q.defer();
Phone.query(function(phones) {
deferred.resolve(phones);
});
return deferred.promise;
}
}
其他回答
这个提交是版本1.1.5及更高版本的一部分,它公开了$resource的$promise对象。包含此提交的ngResource版本允许像这样解析资源:
routeProvider美元
resolve: {
data: function(Resource) {
return Resource.get().$promise;
}
}
控制器
app.controller('ResourceCtrl', ['$scope', 'data', function($scope, data) {
$scope.data = data;
}]);
这个代码片段是依赖注入友好的(我甚至将它与ngmin和uglify结合使用),它是一个更优雅的基于领域驱动的解决方案。
下面的例子注册了一个Phone资源和一个常量phoneRoutes,其中包含了该(Phone)域的所有路由信息。在提供的答案中,我不喜欢解析逻辑的位置——主模块不应该知道任何事情,也不应该被提供给控制器的资源参数的方式所困扰。这样逻辑就保持在同一个域中。
注意:如果你正在使用ngmin(如果你没有:你应该),你只需要按照DI数组约定编写解析函数。
angular.module('myApp').factory('Phone',function ($resource) {
return $resource('/api/phone/:id', {id: '@id'});
}).constant('phoneRoutes', {
'/phone': {
templateUrl: 'app/phone/index.tmpl.html',
controller: 'PhoneIndexController'
},
'/phone/create': {
templateUrl: 'app/phone/edit.tmpl.html',
controller: 'PhoneEditController',
resolve: {
phone: ['$route', 'Phone', function ($route, Phone) {
return new Phone();
}]
}
},
'/phone/edit/:id': {
templateUrl: 'app/phone/edit.tmpl.html',
controller: 'PhoneEditController',
resolve: {
form: ['$route', 'Phone', function ($route, Phone) {
return Phone.get({ id: $route.current.params.id }).$promise;
}]
}
}
});
下一部分是在模块处于configure状态时注入路由数据,并将其应用到$routeProvider。
angular.module('myApp').config(function ($routeProvider,
phoneRoutes,
/* ... otherRoutes ... */) {
$routeProvider.when('/', { templateUrl: 'app/main/index.tmpl.html' });
// Loop through all paths provided by the injected route data.
angular.forEach(phoneRoutes, function(routeData, path) {
$routeProvider.when(path, routeData);
});
$routeProvider.otherwise({ redirectTo: '/' });
});
用这个设置测试路由配置也很简单:
describe('phoneRoutes', function() {
it('should match route configuration', function() {
module('myApp');
// Mock the Phone resource
function PhoneMock() {}
PhoneMock.get = function() { return {}; };
module(function($provide) {
$provide.value('Phone', FormMock);
});
inject(function($route, $location, $rootScope, phoneRoutes) {
angular.forEach(phoneRoutes, function (routeData, path) {
$location.path(path);
$rootScope.$digest();
expect($route.current.templateUrl).toBe(routeData.templateUrl);
expect($route.current.controller).toBe(routeData.controller);
});
});
});
});
你可以在我最新的(即将到来的)实验中看到它的全部荣耀。 虽然这个方法对我来说工作得很好,但我真的想知道为什么$injector在检测到任何是promise对象的注入时不延迟任何东西的构造;这会让事情变得非常非常非常简单。
编辑:使用Angular v1.2(rc2)
我喜欢上面的答案,并从中学到很多东西,但上面的大多数答案都缺少一些东西。
我遇到过类似的情况,我在解析url时使用了从服务器的第一个请求中获取的一些数据。我面临的问题是,如果承诺被拒绝了怎么办。
我正在使用一个自定义提供程序,它曾经返回一个承诺,在配置阶段由$routeProvider的解析解决。
我在这里想强调的是当它这样做时的概念。
它看到url在url栏,然后分别当块在调用控制器和视图被引用到目前为止,很好。
假设我有以下配置阶段的代码。
App.when('/', {
templateUrl: '/assets/campaigns/index.html',
controller: 'CampaignListCtr',
resolve : {
Auth : function(){
return AuthServiceProvider.auth('campaign');
}
}
})
// Default route
.otherwise({
redirectTo: '/segments'
});
在根url在浏览器的第一个块运行得到调用,否则否则被调用。
让我们想象一个场景,我在地址栏中点击rootUrl authserviceprivid .auth()函数被调用。
假设返回的Promise处于拒绝状态,然后呢?
什么都没有渲染。
否则,block将不会被执行,因为它对于任何没有在配置块中定义的url和angularJs的配置阶段未知的url都是如此。
当这个承诺没有得到解决时,我们将不得不处理被解雇的事件。失败时$ routechangeerror在$rootScope上被触发。
它可以被捕获,如下面的代码所示。
$rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
// Use params in redirection logic.
// event is the routeChangeEvent
// current is the current url
// previous is the previous url
$location.path($rootScope.rootPath);
});
在应用程序的运行块中放置事件跟踪代码通常是一个好主意。这段代码只在应用程序的配置阶段之后运行。
App.run(['$routeParams', '$rootScope', '$location', function($routeParams, $rootScope, $location){
$rootScope.rootPath = "my custom path";
// Event to listen to all the routeChangeErrors raised
// by the resolve in config part of application
$rootScope.$on('$routeChangeError', function(event, current, previous, rejection){
// I am redirecting to rootPath I have set above.
$location.path($rootScope.rootPath);
});
}]);
这样我们就可以在配置阶段处理承诺失败。
使用AngularJS 1.1.5
使用AngularJS 1.1.5语法更新justin回答中的“phones”函数。
原:
phones: function($q, Phone) {
var deferred = $q.defer();
Phone.query(function(phones) {
deferred.resolve(phones);
});
return deferred.promise;
}
更新:
phones: function(Phone) {
return Phone.query().$promise;
}
多亏了Angular团队和贡献者,这个版本变得更短了。:)
这也是马克西米利安·霍夫曼的答案。显然,这个commit被放到了1.1.5。
下面是一个适用于Angular 1.0.2的最小工作示例
模板:
<script type="text/ng-template" id="/editor-tpl.html">
Editor Template {{datasets}}
</script>
<div ng-view>
</div>
JavaScript:
function MyCtrl($scope, datasets) {
$scope.datasets = datasets;
}
MyCtrl.resolve = {
datasets : function($q, $http) {
var deferred = $q.defer();
$http({method: 'GET', url: '/someUrl'})
.success(function(data) {
deferred.resolve(data)
})
.error(function(data){
//actually you'd want deffered.reject(data) here
//but to show what would happen on success..
deferred.resolve("error value");
});
return deferred.promise;
}
};
var myApp = angular.module('myApp', [], function($routeProvider) {
$routeProvider.when('/', {
templateUrl: '/editor-tpl.html',
controller: MyCtrl,
resolve: MyCtrl.resolve
});
});
http://jsfiddle.net/dTJ9N/3/
简化版:
因为$http()已经返回了一个承诺(又名deferred),我们实际上不需要创建自己的承诺。我们可以简化MyCtrl。解决:
MyCtrl.resolve = {
datasets : function($http) {
return $http({
method: 'GET',
url: 'http://fiddle.jshell.net/'
});
}
};
$http()的结果包含数据、状态、报头和配置对象,因此我们需要将MyCtrl的主体更改为:
$scope.datasets = datasets.data;
http://jsfiddle.net/dTJ9N/5/