Http服务工厂将未定义的返回到Angular中的控制器
Http Service Factory Returns Undefined to Controller in Angular
我有这个服务,它初始化品牌和实际品牌变量。
.factory('brandsFactory', ['$http', 'ENV', function brandsFactory($http, ENV){
var actualBrand = {};
var brands = getBrands(); //Also set the new actualBrand by default
function getBrands(){
var URL;
console.log('MOCKS enable? ' + ENV.mocksEnable);
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
$http.get(URL).
success(function(data){
console.log('data is :' + data);
actualBrand = data[0];
console.log('Actual brand is ' + actualBrand);
return data;
}).
error(function(){
console.log('Error in BrandsController');
});
}
var setActualBrand = function(activeBrand) {
actualBrand = activeBrand;
};
var isSetAsActualBrand = function(checkBrand) {
return actualBrand === checkBrand;
};
return {
brands : brands,
actualBrand : actualBrand
};
我的控制器是这样的:
/* BrandController to manage the html content of a single brand */
.controller('BrandCController', function(brandsFactory, $scope){
$scope.brands = brandsFactory.brands;
console.log('brands in controller is ' + $scope.brands + ' -- ' + brandsFactory.brands);
});
控制器的问题是它在brandsFactory.brands中未定义,因为它是在服务之前加载的。
我为什么能解决这个问题?我对angular和javascript真的很陌生,也许我做错了很多事情。如果有任何帮助,我将不胜感激,谢谢你。
之所以是undefined
,实际上是因为$http
请求的异步性质。你无法改变这种行为。您将不得不使用最常见的方法来解决这个问题(如果您继续使用Angular和大多数web开发语言,这将成为一种长期的做法)。
什么是承诺?关于它是什么,网上有很多指南。向您解释它的最基本方法是,在异步操作返回之前,promise不会执行。
https://docs.angularjs.org/api/ng/service/$q
http://andyshora.com/promises-angularjs-explained-as-cartoon.html
现在,当您console.log
属性时,它可能/可能还没有加载(事实上,它可能还没有),因此您得到了undefined
。
此外,我对此并不完全确定,但您使用的是工厂提供程序,但您没有从提供程序函数返回任何内容,因此您将其视为一项服务来使用。我不能100%确定Angular是否允许这样做,但最好的做法是从工厂返回您想要创建的实例。
我对angular和javascript真的很陌生,也许我做错了很多事情。如果有任何帮助,我将不胜感激,谢谢你。
你是对的——你做错了很多事。让我们从getBrands
函数开始:
//Erroneously constructed function returns undefined
//
function getBrands(){
var URL;
console.log('MOCKS enable? ' + ENV.mocksEnable);
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
$http.get(URL).
success(function(data){
console.log('data is :' + data);
actualBrand = data[0];
console.log('Actual brand is ' + actualBrand);
//SUCCESS METHOD IGNORES RETURN STATEMENTS
return data;
}).
error(function(){
console.log('Error in BrandsController');
});
return data
语句嵌套在函数内,该函数是$http
.success
方法的参数。它不会向getBrands
函数返回数据。由于getBrands
主体中没有return
语句,因此getBrands
函数返回undefined
。
此外,$http
服务的.success
方法会忽略返回值。因此,CCD_ 16服务将返回解析为CCD_ 17对象而不是CCD_ 18对象的promise。要返回解析为data
对象的promise,请使用.then
方法。
//Correctly constructed function that returns a promise
//that resolves to a data object
//
function getBrands(){
//return the promise
return (
$http.get(URL)
//Use .then method
.then (function onFulfilled(response){
//data is property of response object
var data = response.data;
console.log('data is :' + data);
var actualBrand = data[0];
console.log('Actual brand is ' + actualBrand);
//return data to create data promise
return data;
})
)
}
在onFulfilled
函数中有一个return
语句,在getBrands
函数的主体中有一条return
语句,getBrands
函数将返回一个用data
解决已实现问题的承诺(或用response
对象解决已拒绝问题的承诺)
在控制器中,使用.then
和.catch
方法来解决promise。
brandsFactory.getBrands().then(function onFulfilled(data){
$scope.brand = data;
$scope.actualBrand = data[0];
}).catch( function onRejected(errorResponse){
console.log('Error in brands factory');
console.log('status: ', errorResponse.status);
});
折旧通知1
$http
的遗留承诺方法.success
和.error
已被弃用。请改用标准的.then
方法。
更新
我用
$q
承诺解决它。我在控制器中使用.then
,但我无法使其在工厂中工作。如果你想检查一下。谢谢你的反馈。
//Classic `$q.defer` Anti-Pattern
//
var getBrands = function(){
var defered = $q.defer();
var promise = defered.promise;
$http.get(URL)
.success(function(data){
defered.resolve(data);
})
.error(function(err){
console.log('Error in BrandsController');
defered.reject(err);
});
return promise;
};//End getBrands
这是经典的$q.defer
反模式。.sucesss
和.error
方法被弃用的原因之一是,它们鼓励这种反模式,因为这些方法忽略返回值。
这种反模式的主要问题是它破坏了$http
承诺链,当错误条件处理不当时,承诺将挂起。另一个问题是,程序员经常无法在随后的函数调用中创建新的$q.defer
。
//Same function avoiding $q.defer
//
var getBrands = function(){
//return derived promise
return (
$http.get(URL)
.then(function onFulfilled(response){
var data = response.data;
//return data for chaining
return data;
}).catch(function onRejected(errorResponse){
console.log('Error in BrandsController');
console.log('Status: ', errorResponse.status;
//throw value to chain rejection
throw errorResponse;
})
)
};//End getBrands
此示例显示了如何记录拒绝并将errorResponse
对象扔到链的下游以供其他拒绝处理程序使用。
有关此方面的更多信息,请参阅使用$q
的Angular执行顺序。
此外,为什么角$http
成功/错误被弃用?。
这是"延迟反模式"吗?
我认为您遇到的问题是您的控制器试图访问由$http.get调用设置的属性$http.get返回一个promise,它是对您为获取品牌而提供的URL的异步调用。这意味着进行呼叫的控制器将不会等待加载品牌。
有几个选项可以解决这个问题,但您可以简单地从服务返回promise并在控制器中解决它。
function getBrands(){
var URL;
console.log('MOCKS enable? ' + ENV.mocksEnable);
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
return $http.get(URL);
});
然后在您的控制器中,您可以将成功和错误功能如此…
brandsFactory.brands().then(function(response){
$scope.brand = response.data;
$scope.actualBrand = response.data[0];
}, function(err){
console.log('Error in brands factory');
});
还有其他选项(例如延迟加载或将工厂直接放在您的范围内),但如果您愿意,可以查看这些选项。
我使用$q promise解决它。
.factory('brandsFactory', ['$http', '$q', 'ENV', function brandsFactory($http, $q, ENV){
var actualBrand = {};
var getBrands = function(){
var URL;
var defered = $q.defer();
var promise = defered.promise;
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
$http.get(URL)
.success(function(data){
actualBrand = data[0];
defered.resolve(data);
})
.error(function(err){
console.log('Error in BrandsController');
defered.reject(err);
});
return promise;
};//End getBrands
var setActualBrand = function(activeBrand) {
actualBrand = activeBrand;
};
var isSetAsActualBrand = function(checkBrand) {
return actualBrand === checkBrand;
};
return {
setActualBrand : setActualBrand,
getBrands : getBrands
};
}])//End Factory
在我的控制器中:
/* BrandController to manage the html content of a single brand */
.controller('BrandCController', function(brandsFactory, $scope){
$scope.brands = [];
$scope.actualBrand = {};
$scope.setActualBrand = function(brand){
brandsFactory.setActualBrand(brand);
$scope.actualBrand = brand;
};
brandsFactory.getBrands()
.then(function(data){
$scope.brands = data;
$scope.actualBrand = data[0];
})
.catch(function(errorResponse){
console.log('Error in brands factory, status : ', errorResponse.status);
});
});//End controller
如果我能改进我的答案,请告诉我。我使用了从以前的答案中得到的所有信息。感谢任何帮助你的人。
更新
在我的工厂里移除$q.dedefer反模式。
.factory('brandsFactory', ['$http', '$q', 'ENV', function brandsFactory($http, $q, ENV){
var actualBrand = {};
var getBrands = function(){
var URL;
if(ENV.mocksEnable){
URL = ENV.apiMock + ENV.getBrandsMock;
}else{
URL = ENV.apiURL + ENV.getBrands;
}
return (
$http.get(URL)
.then(function onFulfilled(response){
var data = response.data;
//return data for chaining
return data;
}).catch(function onRejected(errorResponse){
console.log('Error in BrandsController');
console.log('Status: ', errorResponse.status);
return errorResponse;
})
);
};//End getBrands
var setActualBrand = function(activeBrand) {
actualBrand = activeBrand;
};
return {
setActualBrand : setActualBrand,
getBrands : getBrands
};
}]);//End Factory
- angularjs:访问angular控制器中的jquery变量
- 允许在Angular控制器或视图中使用html标记
- 如何在 Angular 控制器中更改 Satellizer 的 $authProvider.loginUrl
- 将 DOM 操作与 Angular 控制器分离 - 需要最佳实践
- 尝试通过 Angular 控制器使用 Google Geocode API 进行地理编码,值未定义
- 如何将列表从C#angular传递到angular控制器
- 如何从html输入类型=“”中只获得Angular控制器变量中的时间值;时间”;
- Angular:控制器范围内的可重用功能
- $scope.form.$error在Angular控制器中未定义
- 如何在单元测试中注入Angular控制器
- 如何将变量从Angular控制器传递到html<脚本></脚本>
- Angular:控制器可以监视服务器属性吗
- 从 Angular 控制器访问不同模块中的角度服务
- 如何在 PopConconfirm 中单击按钮 yes 时调用在 Angular 控制器中声明的删除函数
- 在 Angular 控制器完成加载异步函数后加载 html 视图
- 如何使用 Angular 控制器将变量传递到另一个页面
- 这两个 Angular 控制器之间的功能有什么区别吗?
- Karma + Webpack Angular 控制器未找到
- 如何将范围中的变量注入到 Angular 控制器中
- 从 Angular 控制器调用引导模式