Kimberly BF Kimberly BF - 6 months ago 14
Javascript Question

Http Service Factory Returns Undefined to Controller in Angular

I have this service, that initialize brands and actualBrand variables.

.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
};


And my controller looks like this:

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

});


The problem in the controller is that it gets undefined in brandsFactory.brands because its loaded before the service.

Why can I do to solve this?
Im really new in angular and javascript, maybe Im doing lots of thing wrong.
Any help I would be grateful, thank you.

Answer

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

You are correct -- you are doing lot's of things wrong. Let's start with the getBrands function:

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

The return data statement is nested inside a function which is the argument of an $http .success method. It does not return data to the getBrands function. Since there is no return statement in the getBrands body, the getBrands function returns undefined.

In addition the .success method of the $http service ignores return values. Thus the $http service will return a promise that resolves to a response object instead of a data object. To return a promise that resolves to a data object, use the .then method.

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

With a return statement inside the onFulfilled function and a return statement in the body of the getBrands function, the getBrands function will return a promise that resolves fulfilled with data (or resolves rejected with a response object.)

In the controller, resolve the promise with the .then and .catch methods.

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.


UPDATE

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

This is a classic $q.defer Anti-Pattern. One of the reasons that the .sucesss and .error methods were deprecated was that they encourage this anti-pattern as a result of the fact that those methods ignore return values.

The major problem with this anti-pattern is that it breaks the $http promise chain and when error conditions aren't handled properly the promise hangs. The other problem is that often programmers fail to create a new $q.defer on subsequent invocations of the function.

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

This example shows how to log a rejection and throw the errorResponse object down the chain to be used by other rejection handlers.

For more information on this, see Angular execution order with $q.

Also, Why is angular $http success/error being deprecated?.

And, Is this a “Deferred Antipattern”?

Comments