AngularJS: 具有异步请求的多个依赖服务

AngularJS: Multiple depending services with asynchronous requests

我想构建一个 angular 应用程序,主要用作显示数据,但以稍微动态的方式。我有多个 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();
  }
})();

我已经查看了 this and this 问题以及官方文档,但到目前为止他们只让我了解了...

毕竟,我认为我的问题的原因是 Javascript 中的可见性和范围。在信息服务中(在问题中发布),我使用了一个名为 information 的 "global" 变量,但是在 getInformation() 方法的每个循环的 angular 中,我创建一个具有相同名称的局部变量,这样就不会像我打算的那样将任何内容添加到我的原始变量中。为了完成,我将再次添加这两个服务的最终实现。请注意,我不仅解决了该错误,而且还对信息服务进行了一些重构(如下)。此解决方案如我所愿。

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;

  }
})();