Сервис $http в AngularJS

Я новичок в AngularJS и пытаюсь собрать практическое веб-приложение. В более ранних версиях моего приложения у меня был один контроллер, и я использовал службу $http для извлечения данных из файла JSON, например так

var App = angular.module('testApp', []);
App.controller('firstCtrl', ['$scope', '$http', function($scope, $http) {
    $scope.items = [];
    $http.get('data.json').success(function(data) {
      $scope.items = data;
    });
}]);

Служба $http работала нормально. Затем, по мере продвижения, у меня было несколько контроллеров, и для обмена данными между контроллерами я переместил службу $http в Завод вот так

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

App.factory('DataFactory', ['$http', function($http) {
    var DataFactory = {};
    var items = [];
    $http.get('data.json').success(function(data) {
      items = data;
    });
    DataFactory.data = function() {
        return items;
    };
    return DataFactory;
}]);

App.controller('firstCtrl', ['$scope', 'DataFactory',function($scope, DataFactory) {
    $scope.items = [];
    $scope.items = DataFactory.data();
}]);

А вот HTML

<div ng-controller="firstCtrl">
  <ul>
    <li ng-repeat="item in items">{{item.name}}</li>
  </ul>
</div>

Я не знаю, почему по какой-то причине данные не отображаются после того, как я переместил службу $http в Factory. Что я делаю неправильно, я не вижу никаких ошибок в консоли


person abhi    schedule 23.07.2014    source источник


Ответы (2)


Поскольку за 18 месяцев можно многому научиться, я хотел бы обновить свой ответ. В приведенном ниже примере показано, как я решил бы проблему сегодня. (См внизу оригинальное решение)

function dataFactory($http) {
    //Service interface, all properties and methods will be set to this object.
    var dataFactory={};

    //Instead of using $q, the function will just return the http-promise containing the response data.
    dataFactory.getItems=function() {
        return $http
            .get('data.json')
            .then(function(response) {
                return response.data; 
            });
    }

    //Return object containing the service interface.
    return dataFactory;
}
//Use $inject property to specifiy your DI objects, rather than using array syntax.
dataFactory.$inject=['$http'];

function firstController(DataFactory) {
    //Use this with controllerAs instead of injecting $scope.
    var vm=this;
    vm.items=[];

    DataFactory
        .getItems()
        .then(function(items) {
            vm.items=items;
        }, function(err) {
            //error handler
            alert("Got an error");
        })
}
//same here, use $inject property.
firstController.$inject=['dataFactory'];

angular
    .module('testApp', [])
    .factory('DataFactory', dataFactory)
    .controller('FirstController', firstController);

И HTML

 <!-- Use controller as syntax -->
 <div ng-controller="firstController as first">
     <ul>
         <!-- Reference the controller by value given in controller as statement -->
         <li ng-repeat="item in first.items">{{item.name}}</li>
     </ul>
 </div>

Вот как бы я написал код. Тем не менее, это не то, как я бы это решил. Я бы не стал вносить никаких изменений в службу данных, но я бы изменил реализацию контроллера. Либо я бы resolve the items data через роутер, либо связал бы контроллер и html как directive, либо как 1.5 как component.

Использование директивы

function itemsDirective() {
    function controller(DataFactory) {
        var vm=this;
        vm.items=[];

        DataFactory
            .getItems()
            .then(function(items) {
                vm.items=items;
            }, function(err) {
                //error handler
                alert("Got an error");
            })
    }
    controller.$inject=['dataFactory'];

    return {
        restrict:'E',
        template:'<div ng-controller="firstController as first">
            <ul>
                <li ng-repeat="item in first.items">{{item.name}}</li>
            </ul>
        </div>',
        controller: controller,
        controllerAs: 'first'
    }
}

angular
    .module('testApp')
    .directive('itemsDirective', itemsDirective);

Старый ответ (23 июль 2014, в 20:26)

Потому что значение не устанавливается до того, как оно вернет свое значение. Сказав это, вы, возможно, захотите реструктурировать свою службу (фабрику), также используйте $q для обработки обещания. Рассмотрим следующий пример:

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

App.factory('DataFactory', ['$http', '$q', function($http, $q) {
    var getItems = function() {
        var deffered = $q.defer();

        $http.get('data.json').success(function(data) {
             deffered.resolve(data);
        });

        return deffered.promise;
    };

    return {
       getItems: getItems
    };
}]);

App.controller('firstCtrl', ['$scope', 'DataFactory',function($scope, DataFactory) {
   $scope.items = DataFactory.getItems();
}]);

Обычной практикой является использование $q при работе с асинхронными задачами, такими как http-запрос.

person cbass    schedule 23.07.2014
comment
По крайней мере, в AngularJS 1.2+ невозможно напрямую привязаться к промису. Вам нужно развернуть промис в контроллере и установить переменную $scope.items в обратном вызове then(...). Так что достаточно вернуть посылку из $http.get и развернуть ее в контроллере. Не требуется пользовательской логики промисов (что на самом деле в любом случае не нужно). - person Sabacc; 24.07.2014
comment
$http также настроен как обещание по умолчанию. Он использует «успех» и «ошибка» вместо $ q «тогда». - person Chris; 24.07.2014

Упрощенное решение на основе Ответа cbass, разворачивающего промис в контроллере:

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

App.factory('DataFactory', ['$http', function($http) {
    var getItems = function() {
        return $http.get('data.json');
    };

    return {
       getItems: getItems
    };
}]);

App.controller('firstCtrl', ['$scope', 'DataFactory',function($scope, DataFactory) {
   DataFactory.getItems().success(function(result){
       $scope.items = result;
   });
}]);
person Sabacc    schedule 23.07.2014
comment
Что делать, если у меня есть другой контроллер, которому нужны предметы? Нужно ли мне переделывать это, чтобы получить элементы в этом новом контроллере? Если да, то не буду ли я делать еще один HTTP-запрос? - person abhi; 24.07.2014
comment
Любое из приведенных выше решений сделает http-запрос при вызове функции getItems. Однако вы можете кэшировать ответ в службе, а затем ссылаться на него в своих контроллерах. В этом случае вы можете сделать http-запрос при инициализации службы и/или постоянно обновлять кеш. - person cbass; 24.07.2014
comment
@abhi да, с этим решением каждый контроллер делает свой собственный запрос. Но вы можете использовать опцию cache: true для кэширования результата. Таким образом, второй контроллер будет загружать данные из кеша. см. раздел «Кэширование» в документации здесь: https://docs.angularjs.org/api/ng/service/$http - person Sabacc; 24.07.2014