我正在尝试使用Angular应用程序的主控制器中的$ resource从服务器加载JSON对象。有些路由会将部分加载到ng-view中,其中显示数据,分页等等。
这一切都运行正常,但我的下一个目标是根据点击的链接过滤数据,并更新网址,以便用户可以链接到过滤后的数据(domain.dev/products/brandname)。
只要站点从根URL或任何其他未调用路径到产品的URL加载,一切都很好,但是一旦我尝试从/ products加载站点,它就会因为数据而中断在路径将部分加载到视图中之前不可用。
我想知道一个承诺是否有助于解决这个问题,如果有的话,从哪里开始。我一直在阅读承诺,我很确定这是答案,但我无法使用现有的代码。
我正在使用$ resource,因为它看起来很干净,但也许$ http会在这里做得更好,因为它有更多的选择。
简单地说,当站点从/加载时,一切都很好,因为使用JSON的部分还没有,当它们被加载时,数据就准备好了。但是当加载部分的控制器在网站加载时调用时,数据不存在,当我尝试设置一些变量时,ProductController会抛出错误。由于$ scope.products不存在,因此它没有长度方法。我将部分包含在div中的整个代码包含在ng-show =“products”中,但是在这种情况下我没有按预期工作,因为错误似乎在控制器中抛出。部分仍然加载,但{{vars}}可见。
对此的任何指导将不胜感激。
编辑:我没有尝试将URL参数发送到端点,我实际上是想要所有数据,然后使用Angular进行过滤。基本上,我希望主应用程序下载所有数据(它并不多)并准备就绪,无论/ products / string是否是路径。我的目标是避免为每个过滤器进行HTTP调用。我有这个工作,但只有几个项目等待HTTP结果太延迟了。我的主要问题是我可以获取数据,但是当数据从服务器返回时,ProductController已经运行并抛出错误。基本上,我希望控制器在尝试运行之前等待数据。
编辑2:我发现了问题,这更像是我尝试构建应用的方式。我认为分离代码的最佳方法是将产品放在自己的控制器中,但是由于还想要预先加载数据,我需要在主appController中调用服务。那是我的错。一旦我将所有代码移动到appController 并更改了产品路径中的控制器,它就可以按需运行。我被提示要更多地了解承诺,这很好,我能够使用我学到的东西。仍然存在过滤问题,但这解决了这个问题。
我从这篇文章得到了一些帮助:AngularJS: Waiting for an asynchronous call
以下是工作代码:
elite.controller('productController', function($scope, $routeParams, $rootScope, $location, Products){
$scope.brand = $routeParams.brand;
$rootScope.productPagination = function(){
$scope.currentPage = 1;
$scope.numPerPage = 10;
$scope.maxSize = 6;
$scope.totalItems = $rootScope.products.length;
$scope.numPages = Math.ceil($scope.totalItems / $scope.numPerPage);
$scope.$watch('currentPage + numPerPage', function() {
var begin = (($scope.currentPage - 1) * $scope.numPerPage)
, end = begin + $scope.numPerPage;
$scope.filteredProducts = $rootScope.products.slice(begin, end);
});
}
if (!$rootScope.products){
Products.getProducts().then(function(data){
$rootScope.products = data.products;
$rootScope.productPagination();
});
}else{
$rootScope.productPagination();
}
});
到目前为止,这是app(简明):
var elite = angular.module("elite", ["ngResource", "ngSanitize", "ui.bootstrap"]);
elite.config(['$routeProvider', function($routeProvider){
$routeProvider.when('/', {
templateUrl: 'partials/home.html',
controller: 'homeController'
})
$routeProvider.when('/products', {
templateUrl:'partials/products.html',
controller: 'productController'
})
$routeProvider.when('/products/:brand', {
templateUrl:'partials/products.html',
controller: 'productController'
})
$routeProvider.otherwise({redirectTo :'/'})
}]);
elite.factory('Products', function($resource){
return $resource("/products")
});
elite.controller('appController', function($scope, $location, $routeParams, Products){
Products.get({},function (data){
$scope.products = data.products;
});
});
elite.controller('productController', function($scope, $location, $routeParams, $resource){
$scope.brand = $routeParams.brand;
$scope.currentPage = 1;
$scope.numPerPage = 10;
$scope.maxSize = 6;
$scope.$watch($scope.products, function(){
$scope.numPages = Math.ceil($scope.products.length / $scope.numPerPage);
$scope.$watch('currentPage + numPerPage', function() {
var begin = (($scope.currentPage - 1) * $scope.numPerPage)
, end = begin + $scope.numPerPage;
$scope.totalItems = $scope.products.length;
$scope.filteredProducts = $scope.products.slice(begin, end);
});
});
});
<div ng-show="products">
<h2>{{ brand }} products...</h2>
<h4>{{products.length}} items</h4>
<pagination ng-show="numPages" total-items="totalItems" page="currentPage" max-size="maxSize" class="pagination-small" boundary-links="true" rotate="false" num-pages="numPages"></pagination>
<table class="table table-striped">
<tr ng-repeat="product in filteredProducts">
<td>{{product.brand}}</td>
<td>{{product.title}}</td>
<td ng-bind="product.priceDealer | currency"></td>
<td>{{product.msrp | currency}}<td>
</tr>
</table>
</div>
答案 0 :(得分:3)
我会改变你的例子:
相反:
elite.factory('Products', function($resource){
return $resource("/products")
});
我们可以写:
elite..factory('Products', function($q){
var data = $resource('/products', {},
{
query: {method:'GET', params:{}}}
);
// however I prefer to use `$http.get` instead `$resource`
var factory = {
query: function (selectedSubject) {
var deferred = $q.defer();
deferred.resolve(data);
return deferred.promise;
}
}
return factory;
});
之后,控制器方:
代替:
Products.get({},function (data){
$scope.products = data.products;
});
我们得到承诺回复:
Products.query()
.then(function (result) {
$scope.products = data.products;
}, function (result) {
alert("Error: No data returned");
});
<强>参考强>
promise表示未来值,通常是异步操作的未来结果,并允许我们定义一旦此值可用,或者 发生错误。
我建议你阅读这篇好文 presentation 热门承诺
答案 1 :(得分:1)
您可以resolve
使用$routeProvider
来解决此问题。与controller
属性类似,可以使用resolve
属性。正如文件中提到的那样
应该注入的可选依赖关系图 控制器。如果任何这些依赖是承诺,路由器将 等待他们全部解决或一个被拒绝之前 控制器被实例化。如果所有承诺都得到解决 成功地,已经解决的承诺的值被注入和 触发$ routeChangeSuccess事件。如果有任何承诺 拒绝了$ routeChangeError事件被触发。
您的定义变为
$routeProvider.when('/products/:brand', {
templateUrl:'partials/products.html',
controller: 'productController',
resolve: {
brand:function($resource, $route) {
//Get the param values from $route.current.params
// implement the method that return a promise which resolves to brand data or product data.
}
},
})
然后您可以在控制器中获取此数据
function BrandController($scope,brand) {
//access brand data here
}