AngularJS: несколько зависимых сервисов с асинхронными запросами

Я хочу создать угловое приложение, которое в основном служит для отображения данных, но немного динамично. У меня есть несколько файлов JSON с одинаковой структурой, но разным содержимым, по одному для каждого языка:

res/information_en.json

[
    {
        "name": "Jobs",
        "data": [
            {
                "title": "Title",
                "employer": "Employer",
                "description": "Description",
                "begin": "2015-12",
                "end": "2016-12"
            }
        ]
    },{
        "name": "Personal",
        "data": [
            {
                "firstname": "Christian",
                "lastname": "Steinmeyer"
            }
        ]
    }
]

Немецкая версия:

res/information_de.json

[
    {
        "name": "Jobs",
        "data": [
            {
                "title": "Titel",
                "employer": "Arbeitgeber",
                "description": "Beschreibung",
                "begin": "2015-12",
                "end": "2016-12"
            }
        ]
    },{
        "name": "Personal",
        "data": [
            {
                "firstname": "Christian",
                "lastname": "Steinmeyer"
            }
        ]
    }
]

Кроме того, у меня есть еще один файл JSON, который отслеживает все языки:

res/languages.json

[
    {
        "name": "English",
        "short": "en",
        "active": true
    },{
        "name": "Deutsch",
        "short": "de",
        "active": false
    }
]

По сути, я хочу, чтобы пользователь мог выбрать язык, на котором должна отображаться информация, из доступных, заданных res/languages.json. Для этого я создал первый сервис:

app/services/language-service.js

(function(){ 
  'use strict';

  angular.module('gulpAngularCv').factory('LanguageService', LanguageService);

  /** @ngInject */
  function LanguageService($log, $q, $resource, toastr) {

    var service = {};

    service.getLanguages = getLanguages;

    service.select = select;

    service.getActiveLanguage = getActiveLanguage;




    var initialized = false;

    var languages = [];

    function getLanguages(){
        if (initialized){
            return languages;
        } else {
            initialize().then(
                function success(result){
                    angular.forEach(result, function addLanguage(language){
                        languages.push(language);
                    })
                    initialized = true;
                }, function fail(reject){
                    $log.error("Loading 'res/languages.json' failed.");
                    $log.error(reject);
                    toastr.error('Make sure, it is formatted correctly.', 'Loading language file failed!');

                }
            );
            return languages;
        }
    }

    function initialize(){
        var deferred = $q.defer();
        $resource('res/languages.json').query(
            function success(result){
                deferred.resolve(result);
            }, function fail(reject){
                deferred.reject(reject);
            }
        );
        return deferred.promise;
    }

    function select(language){
        // iterate over all languages
        // deactivate, if active and activate if equal to parameter
    }

    function getActiveLanguage(){
        for (var i = 0; i < languages.length; i++){
            if (languages[i].active){
                return languages[i];
            }
        }
    }

    return service;

  }
})();

Это само по себе работает как шарм при вызове из контроллера. Но, как я уже сказал, я также хотел иметь возможность загружать информацию из соответствующего файла json. Что я пробую со следующей службой:

app/services/information-service.js

(function(){ 
  'use strict';

  angular.module('gulpAngularCv').factory('InformationService', InformationService);

  /** @ngInject */
  function InformationService($log, $q, $resource, toastr, LanguageService) {

    var service = {};

    service.getInformation = getInformation;




    var initialized = {};

    var information = [];

    function getInformation(){
        var language = LanguageService.getActiveLanguage();
        if (initialized === language){
            return information;
        } else {
            initialize(language).then(
                function success(result){
                    angular.forEach(result, function addInformation(information){
                        information.push(information);
                    })
                    initialized = language;
                }, function fail(reject){
                    $log.error("Loading 'res/information_" + language.short + ".json' failed.");
                    $log.error(reject);
                    toastr.error('Make sure, it is formatted correctly.', 'Loading information file failed!');

                }
            );
            return information;
        }
    }

    function initialize(language){
        var deferred = $q.defer();
        $resource("res/information_" + language.short + ".json").query(
            function success(result){
                deferred.resolve(result);
            }, function fail(reject){
                deferred.reject(reject);
            }
        );
        return deferred.promise;
    }

    return service;

  }
})();

Я, в принципе, делаю то же самое, но в этот раз не получится, потому что кажется, что этот сервис внедряется первым, хотя он зависит от другого. Я получаю следующую ошибку в консоли моего браузера:

TypeError: Cannot read property 'short' of undefined
    at initialize (http://localhost:3000/app/services/information-service.js:44:48)
    at Object.getInformation (http://localhost:3000/app/services/information-service.js:25:13)
    at new MainController (http://localhost:3000/app/main/main-controller.js:12:40)
    at invoke (http://localhost:3000/bower_components/angular/angular.js:4535:17)
    at Object.instantiate (http://localhost:3000/bower_components/angular/angular.js:4543:27)
    at http://localhost:3000/bower_components/angular/angular.js:9395:28
    at link (http://localhost:3000/bower_components/angular-route/angular-route.js:977:26)
    at invokeLinkFn (http://localhost:3000/bower_components/angular/angular.js:9039:9)
    at nodeLinkFn (http://localhost:3000/bower_components/angular/angular.js:8533:11)
    at compositeLinkFn (http://localhost:3000/bower_components/angular/angular.js:7929:13) <div ng-view="" class="ng-scope">

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

Для целостности здесь также MainController:

app/main/main-controller.js

(function() {
  'use strict';

  angular
    .module('gulpAngularCv')
    .controller('MainController', MainController);

  /** @ngInject */
  function MainController(InformationService) {
    var vm = this;

    vm.categories = InformationService.getInformation();
  }
})();

Я просмотрел это и этот вопрос уже задан, а также официальная документация, но они меня пока только достали. ..


person Christian Steinmeyer    schedule 07.02.2016    source источник


Ответы (1)


В конце концов, я считаю, что причиной моей проблемы была видимость и области видимости в Javascript. В инфосервисе (размещенном в вопросе) я использовал "глобальную" переменную с именем information, но внутри ангара для каждого цикла в методе getInformation() я создаю локальную переменную с тем же именем, чтобы ничего никогда не было добавлен к моей исходной переменной, как я и собирался. Для завершения я снова добавлю свою окончательную реализацию двух сервисов. Обратите внимание, что я не только исправил ошибку, но и провел рефакторинг в информационном сервисе (ниже). Это решение работает, как я и предполагал.

app/services/language-service.js

(function(){ 
  'use strict';

  angular.module('gulpAngularCv').factory('LanguageService', LanguageService);

  /** @ngInject */
  function LanguageService($log, $q, $resource, toastr) {

    var service = {};

    service.getLanguages = getLanguages;

    service.select = select;

    service.getActiveLanguage = getActiveLanguage;




    var initialized = false;

    var languages = [];

    function getLanguages(){
        if (initialized){
            return languages;
        } else {
            initialize().then(
                function success(result){
                    $log.debug("Loaded languages from 'res/languages.json'");
                    $log.debug(result);
                    angular.forEach(result, function addLanguage(language){
                        $log.debug(language);
                        languages.push(language);
                    })
                    initialized = true;
                }, function fail(reject){
                    $log.error("Loading 'res/languages.json' failed.");
                    $log.error(reject);
                    toastr.error('Make sure, it is formatted correctly.', 'Loading language file failed!');

                }
            );
            return languages;
        }
    }

    function initialize(){
        var deferred = $q.defer();
        $resource('res/languages.json').query(
            function success(result){
                deferred.resolve(result);
            }, function fail(reject){
                deferred.reject(reject);
            }
        );
        return deferred.promise;
    }

    function select(language){
        // iterate over all languages
        // deactivate, if active and activate if equal to parameter
    }

    function getActiveLanguage(){
        for (var i = 0; i < languages.length; i++){
            if (languages[i].active){
                return languages[i];
            }
        }
    }

    return service;

  }
})();

и информационная служба:

app/services/information-service.js

(function(){ 
  'use strict';

  angular.module('gulpAngularCv').factory('InformationService', InformationService);

  /** @ngInject */
  function InformationService($log, $q, $resource, $rootScope, toastr, LanguageService) {

    var service = {};

    service.getCategories = getCategories;


    var model = {};
    model.initialized = null;
    model.categories = [];

    function getCategories(){
        $log.debug("getCategories");
        loadData();
        return model.categories;
    }

    function loadData(){
        var language = LanguageService.getActiveLanguage();
        if (language && model.initialized !== language){
            initialize(language).then(
                function success(data){
                    model.categories.length = 0;
                    angular.forEach(data.categories, function addInformation(datum){
                        $log.debug(datum);
                        model.categories.push(datum);
                    })
                }, function fail(reject){
                    toastr.error('Make sure, it is formatted correctly.', 'Loading information file failed!');
                }
            );
        }
    }

    function initialize(language){
        var deferred = $q.defer();
        $resource("res/information_" + language.short + ".json").get(
            function success(result){
                deferred.resolve(result);
                model.initialized = language;
            }, function fail(reject){
                deferred.reject(reject);
            }
        );
        return deferred.promise;
    }

    return service;

  }
})();
person Christian Steinmeyer    schedule 04.03.2016