AngularJS如何确保Constants服务加载完成后再初始化控制器?
解决AngularJS异步加载Constants服务后初始化控制器的问题
咱们先理清楚当前的场景和问题:
现状与问题
我们有一个Constants服务工厂,一部分常量是硬编码的,另一部分要从服务器的constants.json文件异步加载。然后SettingsController依赖这个服务,但麻烦的是——$http是异步操作,控制器初始化的时候,远程的常量可能还没加载完成,导致读取Constants.somePropertyFromConstantsJson时拿不到预期的值。
先看看原有的代码:
原Constants服务工厂代码
function Constants($http) { var constants = { someFields: someValues }; $http({method: "GET", url: baseUrl + "/constants.json"}).success( function (data, status) { angular.forEach(data, function (value, key) { constants[key] = value; }); } ); return constants; }
原SettingsController代码
function SettingsController($scope, $rootScope, Constants) { $scope.someProperty = Constants.somePropertyFromConstantsJson; // ...其他业务逻辑 }
需求明确
我们需要做到:
- 控制器只有在Constants的异步请求完成后才初始化;
Constants仍然保持成可以通过.或[]直接访问属性的对象;- 尽量少修改现有代码。
同时还要确认这种异步初始化的需求是否符合异步操作的特性。
解决方案
1. 改造Constants服务:给对象加个Promise状态标记
我们可以给返回的constants对象附加一个Promise,用来跟踪异步请求的完成状态,既保留硬编码属性的即时访问能力,又能通知外界异步加载完成。改造后的代码如下:
function Constants($http, $q) { var constants = { someFields: someValues }; // 创建一个deferred对象来管理异步请求的状态 var deferred = $q.defer(); $http({method: "GET", url: baseUrl + "/constants.json"}) .success(function (data, status) { angular.forEach(data, function (value, key) { constants[key] = value; }); // 请求成功完成,标记状态为已完成 deferred.resolve(constants); }) .error(function() { // 处理请求失败的情况,标记状态为失败 deferred.reject("加载常量文件失败"); }); // 给constants对象添加一个ready方法,返回Promise供外部监听 constants.ready = function() { return deferred.promise; }; return constants; }
2. 路由层面控制(推荐,改动最小)
如果你的应用用了AngularJS的路由(比如ngRoute或者ui-router),可以在路由配置里用resolve属性来控制控制器的初始化时机——只有当Constants的异步加载完成后,才会初始化SettingsController。
以ngRoute为例:
angular.module('你的应用模块名') .config(function($routeProvider) { $routeProvider .when('/settings', { templateUrl: 'settings.html', controller: 'SettingsController', resolve: { // 这个依赖会先于控制器加载完成 loadedConstants: function(Constants) { return Constants.ready(); } } }); });
这样修改后,SettingsController会在loadedConstants(也就是Constants的异步请求完成)之后才初始化,此时访问Constants.somePropertyFromConstantsJson就肯定能拿到值了,而且原控制器代码几乎不用改动:
function SettingsController($scope, $rootScope, Constants) { // 现在Constants已经包含了远程加载的所有属性 $scope.someProperty = Constants.somePropertyFromConstantsJson; // ...其他业务逻辑 }
3. 无路由场景:控制器内等待Promise完成
如果你的应用没有用路由,那可以在控制器里显式等待Constants.ready()的Promise完成后再执行依赖远程常量的逻辑:
function SettingsController($scope, $rootScope, Constants) { Constants.ready().then(function(loadedConstants) { // 这里用Constants或者loadedConstants都可以,因为是同一个对象 $scope.someProperty = Constants.somePropertyFromConstantsJson; // ...所有依赖远程常量的逻辑都放在这里 }).catch(function(error) { // 处理加载失败的情况,比如提示用户 console.error(error); }); // 不需要依赖远程常量的逻辑可以直接放在外面,立即执行 }
关于异步初始化需求的合理性
这种需求完全符合异步操作的特性:
- 异步操作的核心就是结果不能立即获取,必须通过回调、Promise等方式等待操作完成后再使用结果;
- 让控制器等待异步服务初始化完成,是避免"未定义"错误的合理方案,保证了数据的完整性;
- 我们的方案没有破坏AngularJS的依赖注入机制,同时保留了Constants对象的原有使用方式,完全符合"尽量少改代码"的要求。
内容的提问来源于stack exchange,提问作者skapral
相关产品推荐
相关产品推荐

