我想知道 AngularJS 是否有一种方法(类似于 Gmail)可以延迟显示新路由,直到使用其各自的服务获取每个模型及其数据之后。
例如,如果有一个ProjectsController
列出了所有项目并且project_index.html
是显示这些项目的模板,则Project.query()
在显示新页面之前将被完全获取。
在此之前,旧页面仍会继续显示(例如,如果我正在浏览另一个页面,然后决定查看此项目索引)。
我想知道 AngularJS 是否有一种方法(类似于 Gmail)可以延迟显示新路由,直到使用其各自的服务获取每个模型及其数据之后。
例如,如果有一个ProjectsController
列出了所有项目并且project_index.html
是显示这些项目的模板,则Project.query()
在显示新页面之前将被完全获取。
在此之前,旧页面仍会继续显示(例如,如果我正在浏览另一个页面,然后决定查看此项目索引)。
$routeProvider 解析属性允许延迟路由更改,直到加载数据。
首先定义一个具有这样resolve
属性的路由。
angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: PhoneListCtrl,
resolve: PhoneListCtrl.resolve}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: PhoneDetailCtrl,
resolve: PhoneDetailCtrl.resolve}).
otherwise({redirectTo: '/phones'});
}]);
请注意,该resolve
属性是在路由上定义的。
function PhoneListCtrl($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}
PhoneListCtrl.resolve = {
phones: function(Phone, $q) {
// see: https://groups.google.com/forum/?fromgroups=#!topic/angular/DGf7yyD4Oc4
var deferred = $q.defer();
Phone.query(function(successData) {
deferred.resolve(successData);
}, function(errorData) {
deferred.reject(); // you could optionally pass error data here
});
return deferred.promise;
},
delay: function($q, $defer) {
var delay = $q.defer();
$defer(delay.resolve, 1000);
return delay.promise;
}
}
请注意,控制器定义包含一个解析对象,该对象声明了控制器构造函数应该可用的内容。这里phones
注入到控制器中,并在resolve
属性中定义。
该resolve.phones
函数负责返回一个Promise。所有的Promise都被收集起来,并且路由更改被延迟到所有的Promise都被解决之后。
工作演示:http : //mhevery.github.com/angular-phonecat/app/#/phones 来源:https : //github.com/mhevery/angular-phonecat/commit/ba33d3ec2d01b70eb5d3d531619bf90153496831
这是一个适用于 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() 已经返回了一个Promise(又名延迟),我们实际上不需要创建我们自己的。所以我们可以简化MyCtrl。决心:
MyCtrl.resolve = {
datasets : function($http) {
return $http({
method: 'GET',
url: 'http://fiddle.jshell.net/'
});
}
};
$http() 的结果包含data、status、headers和config对象,因此我们需要将 MyCtrl 的主体更改为:
$scope.datasets = datasets.data;
我看到有些人问如何使用 angular.controller 方法和缩小友好的依赖注入来做到这一点。因为我刚刚开始工作,所以我觉得有必要回来帮忙。这是我的解决方案(采用原始问题和 Misko 的回答):
angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: PhoneListCtrl,
resolve: {
phones: ["Phone", "$q", function(Phone, $q) {
var deferred = $q.defer();
Phone.query(function(successData) {
deferred.resolve(successData);
}, function(errorData) {
deferred.reject(); // you could optionally pass error data here
});
return deferred.promise;
]
},
delay: ["$q","$defer", function($q, $defer) {
var delay = $q.defer();
$defer(delay.resolve, 1000);
return delay.promise;
}
]
},
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: PhoneDetailCtrl,
resolve: PhoneDetailCtrl.resolve}).
otherwise({redirectTo: '/phones'});
}]);
angular.controller("PhoneListCtrl", [ "$scope", "phones", ($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}]);
由于此代码源自问题/最受欢迎的答案,因此未经测试,但如果您已经了解如何制作对缩小友好的角度代码,它应该会向您发送正确的方向。我自己的代码不需要的一个部分是将“电话”注入到“电话”的解析函数中,我也根本没有使用任何“延迟”对象。
我还推荐这个 youtube 视频http://www.youtube.com/watch?v=P6KITGRQujQ&list=UUKW92i7iQFuNILqQOUOCrFw&index=4&feature=plcp,这对我帮助很大
如果您感兴趣,我决定也粘贴我自己的代码(用咖啡脚本编写),这样您就可以看到我是如何让它工作的。
仅供参考,我事先使用了一个通用控制器来帮助我在几个模型上进行 CRUD:
appModule.config ['$routeProvider', ($routeProvider) ->
genericControllers = ["boards","teachers","classrooms","students"]
for controllerName in genericControllers
$routeProvider
.when "/#{controllerName}/",
action: 'confirmLogin'
controller: 'GenericController'
controllerName: controllerName
templateUrl: "/static/templates/#{controllerName}.html"
resolve:
items : ["$q", "$route", "$http", ($q, $route, $http) ->
deferred = $q.defer()
controllerName = $route.current.controllerName
$http(
method: "GET"
url: "/api/#{controllerName}/"
)
.success (response) ->
deferred.resolve(response.payload)
.error (response) ->
deferred.reject(response.message)
return deferred.promise
]
$routeProvider
.otherwise
redirectTo: '/'
action: 'checkStatus'
]
appModule.controller "GenericController", ["$scope", "$route", "$http", "$cookies", "items", ($scope, $route, $http, $cookies, items) ->
$scope.items = items
#etc ....
]
此提交,这是1.1.5版的一部分以上,暴露$promise
的物体$resource
。包含此提交的 ngResource 版本允许解析如下资源:
$routeProvider
resolve: {
data: function(Resource) {
return Resource.get().$promise;
}
}
控制器
app.controller('ResourceCtrl', ['$scope', 'data', function($scope, data) {
$scope.data = data;
}]);
这个片段是依赖注入友好(我甚至用它在组合ngmin和丑化),这是一个更优雅的领域驱动基础的解决方案。
下面的示例注册了一个Phone 资源和一个常量 phoneRoutes,其中包含该(电话)域的所有路由信息。在提供的答案中,我不喜欢解析逻辑的位置——主module不应该知道任何事情,也不应该对向控制器提供资源参数的方式感到困扰。这样,逻辑就保持在同一个域中。
注意:如果你正在使用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;
}]
}
}
});
下一部分是在module处于配置状态时注入路由数据并将其应用于$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);
});
});
});
});
你可以在我最新的(即将进行的)实验中看到它的盛况。尽管这种方法能正常工作对我来说,我真的不知道为什么$喷油器不误事的建设什么当它检测到的注射什么这是一个Promise的对象; 它会让事情变得更容易。
编辑:使用 Angular v1.2(rc2)