How to set a variable from an $http call then use it in the rest of the application WITHOUT making the whole application asynchronous

后端 未结 4 1789
伪装坚强ぢ
伪装坚强ぢ 2021-01-23 09:05

I have this data

{
  \"config\": {
    \"RESTAPIURL\": \"http://myserver/myrestsite\"
  }
}

and I have this factory that reads that data

相关标签:
4条回答
  • 2021-01-23 09:33

    Adding a bit to what @ThinkingMedia was saying in the comment, with ui-router when defining controllers you can add a resolve parameter.

    In it you can specify some promises that have to resolve before the controller is instantiated, thus you are always sure that the config object is available to the controller or other services that the controller is using.

    You can also have parent/child controllers in ui-router so you could have a RootController that resolves the config object and all other controllers inheriting from RootController

    .state('root', {
        abstract: true,
        template: '<ui-view></ui-view>',
        controller: 'RootController',
        resolve:{
          config: ['api', function(api){
            return api.initialize();
          }       
        }
      });
    

    and your api factory:

    angular.module('myApp').factory('api',
      ["$http", "$q",
      function ($http, $q) {
        var _configObject = null;
    
        function initialize() {
          return $http.get('/scripts/constants/config.json')
          .then(function (data) {
              _configObject = data;
              return data;
          });
        }
    
        // you can call this in other services to get the config object. No need to initialize again
        function getConfig() {
          return _configObject;
        }
    
        return {
          initialize: initialize,
          getConfig: getConfig
        }
      }
      ]
    );
    
    0 讨论(0)
  • 2021-01-23 09:40

    Why don't you initialize the factory when the app is loading and put the variable onto another property? Something like this:

    angular.module('myApp').factory('api', ["$http", "$q",
      function ($http, $q) {
        // store URL in a variable within the factory
        var _URL;
    
        function _initFactory() {
          var deferred = $q.defer();
          $http.get('/scripts/constants/config.json')
          .success(function (data) {
    
            // Set your variable after the data is received
            _URL = data.RESTAPIURL;
            deferred.resolve(data);
    
          });
          return deferred.promise;
        }
    
        function getURL() {
            return _URL;
        }
    
        return {
          initFactory: _initFactory,
          URL: getURL
        }
      }
      ]
    );
    
    
    // While the app is initializing a main controller, or w/e you may do, run initFactory
    //...
    api.initFactory().then(
      // may not need to do this if the URL isn't used during other initialization
    )
    //...
    
    // then to use the variable later
    function _get(creds) {
    
        var deferred = $q.defer();
    
        $http({method: 'GET', url: api.URL + api.AUTH, headers: {
            'Authorization': 'Basic '+creds}
        })
        .success(function (data, status, results, headers) {
            deferred.resolve(results);
        })
        return deferred.promise;
     }
    
    0 讨论(0)
  • 2021-01-23 09:46

    I would pass a callback to the getURL method, and save the URL when it returns. Then I would attach any subsequent requests to that callback. Here I am assuming that you are doing something similar with api.AUTH that you don't have a reference to in your code.

    Pass a callback to the getURL method in the api service.

    angular.module('myApp').factory('api', ["$http", "$q",
    
    function ($http, $q) {
    
        function _getConfiguration() {
            var deferred = $q.defer();
            $http.get('/scripts/constants/config.json')
                .success(function (data) {
                deferred.resolve(data);
            })
                .error(function (data, status) {
                deferred.reject(data, status);
            });
            return deferred.promise;
        }
    
        return {
            getURL: function (cb) {
                var that = this;
                if (that.URL) {
                    return cb(that.URL);
                }
    
                _.getConfiguration().then(function (data) {
                    that.URL = data.config.RESTAPIURL + "/api";
                    cb(that.URL);
                });
            }
        }
    }]);
    

    And in your AuthService, wrap your _get inside a callback like this:

    angular.module('myApp').factory('AuthService', function ($http, $q, api, NotificationService) {
    
        function _get(creds) {
            var deferred = $q.defer();
            var getCallback = function (url) {
    
                $http({
                    method: 'GET',
                    url: url + api.AUTH,
                    headers: {
                        'Authorization': 'Basic ' + creds
                    }
                })
                    .success(function (data, status, results, headers) {
                    deferred.resolve(results);
                })
                    .error(function (data, status) {
                    NotificationService.redirect(status);
                    deferred.reject(data, status);
                });
            };
            api.getURL(getCallback);
            return deferred.promise;
        }
    
        return {
            get: _get
        };
    });
    
    0 讨论(0)
  • 2021-01-23 09:53

    I see you haven't used any $resource's here, but I'm hoping you have a good understanding of them:

    in factories/delay-resource.js:

    'use strict'
    
    angular.module('myApp').factory('delayResource', ['$resource', '$q',
    function($resource, $q){
        var _methods = ['query', 'get', 'delete', 'remove', 'save'];
    
        var shallowClearAndCopy = function(src, dst) {
                dst = dst || {};
    
                angular.forEach(dst, function(value, key){
                    delete dst[key];
                });
    
                for (var key in src) {
                    if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
                        dst[key] = src[key];
                    }
                }
    
                return dst;
            }
    
        var delayResourceFactory = function(baseUrlPromise, url, paramDefaults){
            var _baseUrlPromise = baseUrlPromise,
                _url = url,
                _paramDefaults = paramDefaults;
    
            var DelayResource = function(value){
                shallowClearAndCopy(value || {}, this);
            };
    
            _methods.forEach(function(method){
                DelayResource[method] = function(params, successCB, errCB, progressCB){
                    if (angular.isFunction(params)) {
                        progressCB = successCB;
                        errCB = errHandlers;
                        successCB = params;
                        errHandlers = params = null;
                    }
                    else if (!params || angular.isFunction(params)){
                        progressCB = errCB;
                        errCB = successCB;
                        successCB = errHandlers;
                        params = {};
                    }
    
                    var _makeResultResource = function(url){
                        var promise = $resource(url, _paramDefaults)[method](params);
    
                            (promise.$promise || promise).then(
                                function successHandler(){
                                    var data = arguments[0];
    
                                    if (isInstance){
                                        if (angular.isArray(data))
                                            for (var i = 0; i < data.length; i++)
                                                data[i] = new DelayResource(data[i])
                                        else if (angular.isObject(data))
                                            data = new DelayResource(data)
                                    }
    
                                    successCB.apply(successCB, arguments)
                                    resultDelay.resolve.apply(resultDelay.resolve, arguments)
                                },
                                function(err){
                                    errCB.apply(errCB, arguments)
                                    resultDelay.reject.apply(resultDelay.reject, args)
                                },
                                function(){
                                    progressCB.apply(progressCB, arguments)
                                    resultDelay.notify.apply(resultDelay.notify, arguments)
                                }
                            )
                    }
    
                    var isInstance = this instanceof DelayResource,
                        resultDelay = $q.defer();
    
                    if (!angular.isString(_baseUrlPromise) && angular.isFunction(_baseUrlPromise.then))
                        _baseUrlPromise.then(
                            function successCb(apiObj){
                                _makeResultResource(apiObj.RESTAPIURL + _url)
                            },
                            function successCb(){
                                throw 'ERROR - ' + JSON.stringify(arguments, null, 4)
                            })
                    else
                        _makeResultResource(_baseUrlPromise.RESTAPIURL + _url);
    
                    return resultDelay.promise;
                };
    
    
                DelayResource.prototype['$' + method] = function(){
                    var value = DelayResource[method].apply(DelayResource[method], arguments);
                    return value.$promise || value;
                }
            });
    
            return DelayResource;
        }
    
        return delayResourceFactory;
    }]);
    

    This will be the base factory that all requests to that REST API server will go through.

    Then we need a factories/api-resource.js:

    angular.module('myApp').factory('apiResource', ['delayResource', 'api', function (delayResource, api) {
        return function (url, params) {
            return delayResource(api.URL(), url, params);
        };
    }])
    

    Now all factories created will just have to call the apiResource to get a handle on a resource that will communicate with the REST API

    Then in a file like factories/account-factory.js

    angular.module('myApp').factory('AuthRoute', ['apiResource', 'api', function (apiResource, api) {
         return apiResource(api.AUTH);
    }]);
    

    Now in factories/auth-service.js:

    'use strict';
    
    angular.module('myApp').factory('AuthService', ['$q', 'AuthRoute', 'NotificationService', function ($q, AuthRoute, api, NotificationService) {
        function _get(creds) {
            var deferred = $q.defer();
    
            AuthRoute.get()
                .then(
                    function successCb(results){
                        deferred.resolve(results);
                    },
                    function errCb(){
                        // cant remember what comes into this function
                        // but handle your error appropriately here
    
                        //NotificationService.redirect(status);
                        //deferred.reject(data, status);
                    }
                );
    
            return deferred.promise;
        }
    
        return {
            get:_get
        };
    }]);
    

    As you can imagine, I haven't been able to test it yet, but this is the basis. I'm going to try create a scenario that will allow me to test this. In the mean time, feel free to ask questions or point out mistakes made

    Late Addition Forgot to add this:

    'use strict';
    
    angular.module('myApp').factory('api', ["$http", "$q", function ($http, $q) {
      var restApiObj,
          promise;
    
      function _getConfiguration() {
        if (restApiObj)
          return restApiObj;
    
        if (promise)
          return promise;
    
        promise = $http.get('/scripts/constants/config.json')
            .then(function (data) {
              restApiObj = data;
              promise = null;
              return data;
            },
            function (data, status) {
              restApiObj = null;
              promise = null;
            });
        return promise;
      }
    
      return {
        URL: _getConfiguration
      }
    }]);
    

    Continuing with the ui-router scenario

    .state('member-list', {
        url: '/members?limit=&skip='
        templateUrl: '/views/members/list.html',
        controller: 'MemberListCtrl',
        resolve:{
          members: ['$stateParams', 'MembersLoader', function($stateParams,MembersLoader){
            return MembersLoader({skip: $stateParams.skip || 0, limit: $stateParams.limit || 10});
          }       
        }
     });
    

    factory

    .factory('MemberRoute', ['apiResource', function(apiResource){
        return apiResource('/members/:id', { id: '@id' });
    }])
    .factory('MembersLoader', ['MembersRoute', function(MembersRoute){
        return function(params){
            return MemberRoute.query(params);
        };
    }])
    .factory('MemberFollowRoute', ['apiResource', 'api', function(apiResource, api){
        return apiResource(api.FOLLOW_MEMBER, { id: '@id' });
    }])
    

    controller

    .controller('MemberListCtrl', ['$scope', 'members', 'MemberRoute', 'MemberFollowRoute', function($scope, members, MemberRoute, MemberFollowRoute){
        $scope.members = members;
    
        $scope.followMember = function(memberId){
            MemberFollowRoute.save(
                { id: memberId },
                function successCb(){
                    //Handle your success, possibly with notificationService
                },
                function errCb(){
                    // error, something happened that doesn't allow you to follow memberId
                    //handle this, possibly with notificationService
                }
            )
        };
    
        $scope.unfollowMember = function(memberId){
            MemberFollowRoute.delete(
                { id: memberId },
                function successCb(){
                    //Handle your success, possibly with notificationService
                },
                function errCb(){
                    // error, something happened that doesn't allow you to unfollow memberId
                    //handle this, possibly with notificationService
                }
            )
        };
    }]);
    

    With all this code above, you will never need to do any sort of initialization on app start, or in some abstract root state. If you were to destroy your API config every 5 mins, there would be no need to manually re-initialize that object and hope that something isn't busy or in need of it while you fetch the config again.

    Also, if you look at MembersRoute factory, the apiResource abstracts/obscures the api.URL() that you were hoping not to have to change everywhere. So now, you just provide the url that you want to make your request to, (eg: /members/:id or api.AUTH) and never have to worry about api.URL() again :)

    0 讨论(0)
提交回复
热议问题