Angular - лучшая практика для извлечения данных из метода Factory

Я ищу информацию о том, как лучше всего получить данные из локального файла JSON и обработать ответ. После просмотра переполнения стека у меня возникли смешанные мысли, так как я видел несколько способов сделать одно и то же (хотя нет объяснения, почему один может или не может быть предпочтительным).

По сути, у меня есть приложение Angular, которое использует фабрику для извлечения данных из файла JSON; Затем я жду ответа для разрешения в моем контроллере, прежде чем использовать его в моем HTML-файле, как показано ниже:

Опция 1

Фабрика:

comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';

return {
 retrieveInfo: function() {
  return $http.get(retrievalFile);
 }
}

}]);

контроллер:

comparison.controller('comparisonController', ['$scope', 'Info', function($scope, Info) {

Info.retrieveInfo().then(function(response) {
  $scope.info = response.data;
});

}]);

Мое главное утверждение состоит в том, чтобы выяснить, когда лучше дождаться ответа, или если он вообще имеет значение. Я играю с мыслью о том, чтобы фабрика вернула выполненное обещание и жду, пока контроллер также получит данные. На мой взгляд, лучше всего абстрагироваться от извлечения всех данных из контроллера и на фабрику, но я не уверен, распространяется ли это на ожидание фактических данных, которые будут возвращены внутри самой фабрики. Имея это в виду, я не уверен, стоит ли выбирать вариант 1 или вариант 2, и буду очень признателен за отзывы более опытных / квалифицированных разработчиков!

Вариант 2

Фабрика:

comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';

return {
  retrieveInfo: function() {
    return $http.get(retrievalFile).then(function(response) {
      return response.data;
    });
  }
}

}]);

контроллер:

comparison.controller('comparisonController', ['$scope', 'Info', function($scope, Info) {

Info.retrieveInfo().then(function(response) {
  $scope.info = response;
});

}]);

Спасибо за любой вклад / предложения заранее!

4 ответа

Решение

Это зависит от того, что ожидает ваш контроллер и как вы настраиваете приложение. Вообще я всегда иду со вторым вариантом. Это потому, что у меня обычно есть глобальные обработчики ошибок или успеха во всех запросах API, и у меня есть общий доступ api service, Что-то вроде ниже.

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

app.service('ApiService', ['$http', function($http) {
    var get = function(url, params) {
    $http.get(url, { params: params })
        .then(handleSuccess, handleError);
  };

  // handle your global errors here
  // implementation will vary based upon how you handle error
  var handleError = function(response) {
    return $q.reject(response);
  };

  // handle your success here
  // you can return response.data or response based upon what you want
  var handleSuccess = function(response) {
    return response.data;
  };
}]);

app.service('InfoService', ['ApiService', function(ApiService) {
    var retrieveInfo = function() {
    return ApiService.get(retrievalFile);

    /**
    // or return custom object that your controller is expecting
    return ApiService.get.then(function(data) {
      return new Person(data);
    });
    **//
  };

  // I prefer returning public functions this way
  // as I can just scroll down to the bottom of service 
  // to see all public functions at one place rather than
  // to scroll through the large file
  return { retrieveInfo: retrieveInfo };
}]);

app.controller('InfoController', ['InfoService', function(InfoService) {
  InfoService.retrieveInfo().then(function(info) {
    $scope.info = info;
  });
}])

Или, если вы используете маршрутизатор, вы можете разрешить данные в контроллер. Поддержка ngRouter и uiRouter разрешает:

$stateProvider.state({
    name: 'info',
  url: '/info',
  controller: 'InfoController',
  template: 'some template',
  resolve: {
    // this injects a variable called info in your controller
    // with a resolved promise that you return here
    info: ['InfoService', function(InfoService) {
        return InfoService.retrieveInfo();
    }]
  }
});

// and your controller will be like
// much cleaner right
app.controller('InfoController', ['info', function(info) {
    $scope.info = info;
}]);

Это действительно просто предпочтение. Мне нравится думать об этом с точки зрения API. Какой API вы хотите показать? Вы хотите, чтобы ваш контроллер получил полный ответ, или вы хотите, чтобы ваш контроллер просто располагал данными, которые оборачивает ответ? Если вы только собираетесь использовать response.data тогда вариант 2 работает отлично, так как вам никогда не придется иметь дело ни с чем, кроме данных, которые вас интересуют.

Хорошим примером является приложение, которое мы только что написали, где я работаю. У нас есть два приложения: внутренний API и наше внешнее приложение Angular. Мы создали сервис-оболочку API в клиентском приложении. В самом сервисе мы размещаем .catch для любой из конечных точек API, которые имеют документированные коды ошибок (мы использовали Swagger для документирования и определения нашего API). В этом .catch мы обрабатываем эти коды ошибок и возвращаем правильную ошибку. Когда наши контроллеры / директивы используют сервис, они возвращают гораздо более строгий набор данных. Если возникает ошибка, то пользовательский интерфейс обычно безопасен, просто отображая сообщение об ошибке, отправленное из службы оболочки, и ему не нужно беспокоиться о просмотре кодов ошибок.

Аналогично, для успешных ответов мы делаем большую часть того, что вы делаете в варианте 2. Во многих случаях мы уточняем данные до того, что минимально полезно в реальном приложении. Таким образом, мы сохраняем большую часть сбора и форматирования данных в сервисе, а остальной части приложения намного меньше. Например, если нам нужно создать объект на основе этих данных, мы просто сделаем это, вернув объект в цепочку обещаний, чтобы контроллеры не делали это повсеместно.

Хороший вопрос. Пара моментов:

  1. Контроллеры должны быть ориентированы на просмотр, а не на данные, поэтому вы хотите удалить логику данных из контроллера и сосредоточиться на бизнес-логике.
  2. Модели (M в MVC) представляют собой представление данных вашего приложения и будут содержать логику данных. В случае Angular это будет сервисный или заводской класс, как вы правильно отметили. Почему это хорошо, например:

    2.1 AccountsController (может быть введено несколько моделей данных)

    2.1.1 UserModel  
    2.1.2 AuthModel  
    2.1.3 SubscriptionModel  
    2.1.4 SettingsModel
    

Существует множество способов приблизиться к подходу на основе модели данных, но я бы сказал, что вашим классом обслуживания должна быть модель REST данных, т. Е. Получение, хранение, кэширование, проверка и т. Д. Я включил базовый пример, но предлагаю вам изучить JavaScript OOP как это поможет вам правильно ориентироваться в построении моделей данных, коллекций и т. д.

Ниже приведен пример класса обслуживания для управления вашими данными. Обратите внимание, что я не тестировал этот код, но он должен дать вам начало.

ПРИМЕР:

    (function () {
        'use strict';

        ArticleController.$inject = ['$scope', 'Article'];
        function ArticleController($scope, Article) {
            var vm = this,
                getArticles = function () {
                    return Article.getArticles()
                        .then(function (result) {
                            if (result) {
                                return vm.articles = result;
                            }
                        });
                };


            vm.getArticles = getArticles;
            vm.articles = {};
            // OR replace vm.articles with $scope if you prefer e.g.
            $scope.articles = {};

            $scope.userNgClickToInit = function () {
                vm.getArticles();
            };

            // OR an init on document ready
            // BUT to honest I would put all init logic in service class so all in calling is init in ctrl and model does the rest
            function initArticles() {
                vm.getArticles();

                // OR chain
                vm.getArticles()
                    .then(getCategories); // doesn't here, just an example

            }

            initArticles();
        }

        ArticleModel.$inject = ['$scope', '$http', '$q'];
        function ArticleModel($scope, $http, $q) {
            var model = this,
                URLS = {
                    FETCH: 'data/articles.json'
                },
                articles;

            function extract(result) {
                return result.data;
            }

            function cacheArticles(result) {
                articles = extract(result);
                return articles;
            }

            function findArticle(id) {
                return _.find(articles, function (article) {
                    return article.id === parseInt(id, 10);
                })
            }

            model.getArticles = function () {
                return (articles) ? $q.when(articles) : $http.get(URLS.FETCH).then(cacheArticles);
            };

            model.getArticleById = function (id) {
                var deferred = $q.defer();
                if (articles) {
                    deferred.resolve(findArticle(id))
                } else {
                    model.getBookmarks().then(function () {
                        deferred.resolve(findArticle(id))
                    })
                }
                return deferred.promise;
            };

            model.createArticle = function (article) {
                article.id = articles.length;
                articles.push(article);
            };

            model.updateArticle = function (bookmark) {
                var index = _.findIndex(articles, function (a) {
                    return a.id == article.id
                });

                articles[index] = article;
            };

            model.deleteArticle = function (article) {
                _.remove(articles, function (a) {
                    return a.id == article.id;
                });
            };
        }

        angular.module('app.article.model', [])
        .controller('ArticleController', ArticleController)
        .service('Article', ArticleModel);

    })()

Я бы выбрал второй вариант, поскольку ваши варианты в основном одинаковы. Но давайте посмотрим, когда мы добавим модель структуры, как Person предположим.

comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';

return {
  retrieveInfo: function() {
    return $http.get(retrievalFile).then(function(response) {
      //we will return a Person...
      var data = response.data;
      return new Person(data.name, data.age, data.gender);
    });
  }
}

}]);

Это действительно просто, но если вам нужно отобразить более сложные данные в объектные модели (вы получаете список людей с их собственными элементами... и т. Д.), То когда все становится сложнее, вы, вероятно, захотите добавить службу в обрабатывать отображение между данными и моделями. Ну у вас есть другой сервис DataMapper (пример), если вы выберете свой первый вариант, вам придется ввести DataMapper в ваш контроллер, и вам нужно будет сделать запрос через ваш завод, и сопоставить ответ с введенным сервисом. И тогда вы, вероятно, скажете: "Должен ли я иметь весь этот код здесь?... ну наверное нет.

Это гипотетический случай, то, что очень важно, - это то, как вы чувствуете структурирование своего кода, а не его архитектуру, которую вы не поймете. И в конце взгляните на это: https://en.wikipedia.org/wiki/SOLID_(object-oriented_design) и изучите больше информации об этих принципах, но сфокусируйтесь на javascript.

Другие вопросы по тегам