Http服务工厂将未定义的返回到Angular中的控制器

Http Service Factory Returns Undefined to Controller in Angular

本文关键字:Angular 控制器 返回 Http 工厂 未定义 服务      更新时间:2023-09-26

我有这个服务,它初始化品牌和实际品牌变量。

.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