Tengo este servicio, que inicializa las marcas y las variables realBrand.

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

Y mi controlador se ve así:

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

    });

El problema en el controlador es que no se define en brandsFactory.brands porque se carga antes del servicio.

¿Por qué puedo hacer para resolver esto? Soy realmente nuevo en angular y javascript, tal vez estoy haciendo muchas cosas mal. Cualquier ayuda se lo agradecería, gracias.

1
Kimy BF 10 may. 2016 a las 03:13

4 respuestas

La mejor respuesta

Soy realmente nuevo en angular y javascript, tal vez estoy haciendo muchas cosas mal. Cualquier ayuda se lo agradecería, gracias.

Estás en lo correcto, estás haciendo muchas cosas mal. Comencemos con la función 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');
 });

La declaración return data está anidada dentro de una función que es el argumento de un método $http .success. no devuelve datos a la función getBrands. Como no hay una instrucción return en el cuerpo getBrands, la función getBrands devuelve undefined.

Además, el método .success del servicio $http ignora los valores de retorno. Por lo tanto, el servicio $http devolverá una promesa que se resuelve en un objeto response en lugar de un objeto data. Para devolver una promesa que se resuelva en un objeto data, use el método .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;
            })
     )
}

Con una declaración return dentro de la función onFulfilled y una declaración return en el cuerpo de la función getBrands, la función getBrands devolverá una promesa que se resuelve cumplida con data (o resuelve rechazado con un objeto response).

En el controlador, resuelva la promesa con los métodos .then y .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);
});

Aviso de desuso 1

Los $http métodos de promesa heredados .success y .error han quedado en desuso. Utilice el método estándar .then en su lugar.


Actualizar

Yo uso $q promesa para resolverlo. Uso el .then en el controlador, pero no pude hacerlo funcionar en la fábrica. Si quieres comprobarlo. Y gracias por los comentarios.

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

Este es un clásico $q.defer Anti-Pattern. Una de las razones por las que los métodos .sucesss y .error fueron obsoletos fue que fomentan este antipatrón como resultado del hecho de que esos métodos ignoran los valores de retorno .

El principal problema con este antipatrón es que rompe la cadena de promesa $http y cuando las condiciones de error no se manejan correctamente, la promesa se cuelga. El otro problema es que a menudo los programadores no pueden crear un nuevo $q.defer en invocaciones posteriores de la función.

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

Este ejemplo muestra cómo registrar un rechazo y lanzar el objeto errorResponse por la cadena para que lo utilicen otros manejadores de rechazo.

Para obtener más información al respecto, consulte Orden de ejecución angular con $q.

Además, ¿Por qué el $http éxito / error angular está en desuso?.

Y, ¿Es este un "antipatrón diferido"?

2
Community 23 may. 2017 a las 12:19

Creo que el problema que está teniendo es que su controlador está tratando de acceder a una propiedad que se establece mediante una llamada $ http.get. $ http.get devuelve una promesa, que es una llamada asincrónica a la URL que está proporcionando para obtener sus marcas. Eso significa que el controlador que realiza la llamada no esperará a que se carguen las marcas.

Hay algunas opciones para resolver este problema, pero simplemente puede devolver la promesa del servicio y resolverla en el controlador.

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

Luego, en su controlador, puede poner las funciones de éxito y error como tal ...

brandsFactory.brands().then(function(response){
  $scope.brand = response.data;
  $scope.actualBrand = response.data[0];
}, function(err){
   console.log('Error in brands factory');
});

También hay otras opciones (como la carga diferida o poner la fábrica directamente en su alcance), pero puede consultarlas si lo desea.

1
sourdoughdetzel 10 may. 2016 a las 00:30

Lo resuelvo usando $ q promesa.

.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

Y en mi controlador:

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

Si puedo mejorar mi respuesta, hágamelo saber. Utilizo toda la información que obtuve de las respuestas anteriores. Gracias a cualquiera que ayude.

ACTUALIZACIÓN

Eliminar $ q.defer Anti-Pattern en mi fábrica.

.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
0
Community 23 may. 2017 a las 11:48

La razón por la cual es undefined es en realidad debido a la naturaleza asincrónica de las solicitudes $http. No hay nada (bueno, básicamente nada) que pueda hacer para cambiar este comportamiento. Tendrá que resolver esto usando la forma más común (y si continúa con Angular y la mayoría de los lenguajes de desarrollo web, esto se convertirá en una práctica de larga data) con promesas.

¿Qué es una promesa? Hay muchas pautas en línea para lo que es. La forma más básica de explicárselo es que una promesa no se ejecutará hasta que regrese la operación asincrónica.

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

En este momento, cuando console.log la propiedad, puede / no haber sido cargada (de hecho, probablemente no), por lo tanto, obtiene undefined.

Además, no estoy exactamente seguro de esto, pero está utilizando el proveedor de fábrica, pero no devuelve nada de la función del proveedor, por lo tanto, lo está utilizando como si fuera un servicio. No estoy 100% seguro de si Angular lo permite, pero sin duda la mejor práctica es devolver la instancia de lo que desea crear de fábrica.

3
georgeawg 10 may. 2016 a las 04:27