Using factory and http in Angular JS

2019-08-04 19:32发布

问题:

I am just getting started with angular and saw that you can define a factory to use in multiple controllers.

I have the below code, which is returning my result in the 'success' method fine. But not outside of the function.

I have a feeling its to do with async, but I'm not too sure how to go forward with it, if it is that.

Any help would be appreciated, so thanks in advanced.

var abyssApp = angular.module('abyssApp', []);

abyssApp.factory('abyssData', function($http) {
    var result,
        products = $http.get('http://abyss.local/products.php');

    products.success(function(data) {
        result = angular.fromJson(data);
        console.log('success: ', result);  // object with data in
    });

    console.log(result);  // undefined
});

abyssApp.controller('ItemListCtrl', function($scope, abyssData) {
    $scope.items = abyssData;
    console.log('abyssData: ', abyssData);  // undefined
});

回答1:

Here's some edits. Note that I'm doing this without really checking, so typeos and mistakes are altogether possible, but the gist of it should make sense.

var abyssApp = angular.module('abyssApp', []);

abyssApp.service('abyssData', function($http) {
    this.products = $http.get('http://abyss.local/products.php');
});

abyssApp.controller('ItemListCtrl', function($scope, abyssData) {
    abyssData.products.then(function(data) {
        console.log(data);
        $scope.items = data;
    }).catch(function(err){console.log(err);});
});

I made it a service rather than a factory since you really only need a service (singleton) there. A factory would be appropriate if there were different URLs you'd be calling, and wanted to do something like product = new abyssData('potatoes') or something.

Generally speaking, $http returns a promise object, which itself isn't the data you need, but instead will resolve to the data you need. This promise pattern is repeated a lot in angular (and is getting more and more popular in other javascript venues, because it's awesome (IMO)). That means, on the promise object, you call the .then() method and pass in a function to be called when the promise is resolved. The $http resolves with the result of the XHR. Other promises resolve with other things.

At the end of the promise .then stack, it's good form to put a .catch() call, because exceptions thrown inside a promise generally go into the bitbucket unless you explicitly look for them. It's an in-general flaw to the way promises work, and means you have to be careful for exceptions. With older JS engines, you need to change that to .then()'catch', as catch is kind of a funny word in places. But I'm 90% sure that any JS environment that chokes on .catch won't run angular anyway.



回答2:

In 'ItemListCtrl' you should inject object created from 'abyssData' factory. Then, it should return $http promise and directly in controller, in success callback, you should assign returned result to $scope.items. It's just one, example way to do this, there's a plenty of methods and patterns on how to resolve such issues in Angular.

console.log(result);  // undefined

logs 'undefined', because when you call this line, there's no response yet. Try to figure out, what promise is and how exactly AJAX calls work, especially what 'success' and 'error' should be defined.



回答3:

Probably three things:

1) Declare the dependency explicit:

abyssApp.controller('ItemListCtrl', function($scope, abyssData) {

for

abyssApp.controller('ItemListCtrl', ['$scope', 'abyssData', function($scope, abyssData) {

2) To use the value you want, the factory must return that value so in the end you should be return the "result" variable. Try changing "result" which is a common variable name in xhr requests to prevent overrides.

3) To prevent accessing the data before the return, try to give the method instead of the value:

abyssApp.factory('abyssData', function($http) {
    return {
        get: function(successCallback){ //your callback function
            $http.get('http://abyss.local/products.php')
                .success(function(){
                    if(successCallback){
                        successCallback.apply(arguments);
                    }
                });
        }
    }
}

To use change this:

$scope.items = abyssData;

For this

abyssData.get(function(data){
    $scope.items = data;
});


回答4:

You'll need to expose some of the data or the methods from the factory so that the controller can access it. At the moment there is nothing exposed by the factory.

Depending on your use case, I would suggest something like this:

var abyssApp = angular.module('abyssApp', []);

abyssApp.factory('abyssData', function($http) {
    function abyssData() {
        var self = this;
        self.products = null;

        self.getProducts = function(){
            // return products if they are set already (cache)
            if (self.products) { return self.products; }
            $http.get('http://abyss.local/products.php').success(function(data){
                self.products = data;
                return self.products;
            });
        };
    }
    return new abyssData();
});

abyssApp.controller('ItemListCtrl', function($scope, abyssData) {
    $scope.items = abyssData.getProducts();
});

However, both approaches seem unnecessary to have a factory to encapsulate such a simple query?



回答5:

You have it the other way around. First, the factory should return a value. The value is a singleton. Which means that after the factory is executed, it will not execute itself more than once. What you could do is to return an object which has a reference to your results.

abyssApp.factory('abyssData', function($http) {
    var ret = {},
        products = $http.get('http://abyss.local/products.php')

    products.success(function(data) {
        ret.result = angular.fromJson(data);
        console.log('success: ', result);  // object with data in
    });

    console.log(result);  // undefined

    return ret;
});

abyssApp.controller('ItemListCtrl', function($scope, abyssData) {
    $scope.items = abyssData.result;
    console.log('abyssData: ', abyssData);  // undefined
});

But event this solution doesn't smell very good. Unless your list of objects will never get updated. It would make more sense to return a resource that can be queried when needed. It's not certain that when the controller will get loaded, that the results will be already loaded. Which means that accessing abyssData.result might return undefined at first and then something else later.

But to be honest, what you're trying to do should be done with this:

https://docs.angularjs.org/api/ngResource/service/$resource

You could have a factory like this:

abyssApp.factory('AbyssData', ['$resource', function($resource) {
    return $resource('http://abyss.local/products.php')
}])

abyssApp.factory('abyssData', ['AbyssData', function(AbyssData) {
   return AbyssData.query();
}])

Check this out: https://docs.angularjs.org/api/ngResource/service/$resource