Http 服务工厂 Returns 未定义到 Angular 中的控制器

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);

    });

控制器中的问题是它在 brandsFactory.brands 中未定义,因为它在服务之前加载。

我为什么要解决这个问题? 我真的是 angular 和 javascript 的新手,也许我做错了很多事。 任何帮助,我将不胜感激,谢谢。

之所以是undefined其实是因为$http请求的异步性。没有什么(好吧,基本上没有)可以改变这种行为。您将不得不使用最常见的方式(如果您继续使用 Angular 和大多数 Web 开发语言,这将成为一种长期的做法)和承诺来解决这个问题。

什么是承诺?关于它是什么,网上有很多指南。最基本的解释方式就是一个promise要到异步操作returns.

才会执行

https://docs.angularjs.org/api/ng/service/$q
http://andyshora.com/promises-angularjs-explained-as-cartoon.html

现在,当你 console.log 属性 时,它 may/may 还没有被加载(事实上,它可能还没有)因此你得到 undefined.

此外,我对此不太确定,但是您正在使用工厂提供程序,但您没有 return 提供程序功能中的任何内容,因此您将其用作服务.我不是 100% 确定 Angular 是否允许这样做,但当然最好的做法是 return 你想从工厂创建的实例。

我认为您遇到的问题是您的控制器正在尝试访问由 $http.get 调用设置的 属性。 $http.get return 是一个承诺,它是对您提供的 URL 的异步调用以获取您的品牌。这意味着发出调用的控制器不会等待加载品牌。

有几个选项可以解决这个问题,但您可以简单地 return 服务的承诺并在控制器中解决它。

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');
});

还有其他选项(例如延迟加载或将工厂直接放在范围内),但如果您愿意,可以查看这些选项。

Im really new in angular and javascript, maybe Im doing lots of thing wrong. Any help I would be grateful, thank you.

你是对的——你做错了很多事。让我们从 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 方法的参数。它 而不是 return 数据到 getBrands 函数。由于 getBrands 主体中没有 return 语句,因此 getBrands 函数 returns undefined.

此外 $http 服务的 .success 方法 忽略 return 值。因此 $http 服务将 return 一个解析为 response 对象而不是 data 对象的承诺。要 return 解析为 data 对象的承诺,请使用 .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 函数将 return 用 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);
});

Deprecation Notice1

The $http legacy promise methods .success and .error have been deprecated. Use the standard .then method instead.


更新

I use $q promise to solve it. I use the .then in the controller, but I couldn't make it work in the factory. If you want to check it. And thanks for the feedback.

//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

这是一个.sucesss.error 方法被弃用的原因之一是它们鼓励这种反模式,因为这些方法 忽略 return 值.

此反模式的主要问题是它破坏了 $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 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