[英]Http Service Factory Returns Undefined to Controller in Angular
我有这项服务,用于初始化品牌和ActualBrand变量。
.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);
});
控制器中的问题是,在brandFactory.brands中无法对其进行定义,因为它是在服务之前加载的。
我为什么可以解决这个问题? 我在angular和javascript中真的很新,也许我做错了很多事情。 我将不胜感激,谢谢。
之所以undefined
,实际上是由于$http
请求的异步性质。 您无法采取任何措施(嗯,基本上什么也没有)来改变这种行为。 您将不得不使用最常见的方式来解决这个问题(如果继续使用Angular和大多数Web开发语言,这将成为一种长期的做法)。
什么是诺言? 在线上有很多关于它的指导原则。 向您解释它的最基本方法是,在异步操作返回之前,承诺将不会执行。
https://docs.angularjs.org/api/ng/service/$q
http://andyshora.com/promises-angularjs-explained-as-cartoon.html
现在,当您使用console.log
属性时,它可能/可能尚未加载(实际上,它可能尚未加载),因此您undefined
。
此外,我对此不太确定,但是您使用的是工厂提供程序,但是您没有从提供程序函数中返回任何内容,因此您将其当作服务来使用。 我不确定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
方法将忽略返回值。 因此, $http
服务将返回解析为response
对象而不是data
对象的promise。 要返回解析为data
对象的.then
,请使用.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;
})
)
}
有了return
里面声明onFulfilled
功能和return
的身体声明getBrands
功能, getBrands
函数将返回解决与履行承诺data
(与拒绝或做出决议response
的对象。)
在控制器中,解决与承诺.then
和.catch
方法。
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
方法.success
和.error
已过时。 请使用标准的.then
方法。
我用
$q
promise解决。 我在控制器中使用了.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
Anti-Pattern 。 不建议使用.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的异步调用。 这意味着发出呼叫的控制器将不会等待品牌加载。
有一些解决此问题的选项,但是您可以简单地从服务中返回承诺并在控制器中解决它。
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.defer 反模式 。
.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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.