如何构造一个 Angular 服务以便它可以处理异步调用?
How to structure an Angular service so it can handle asynchronous calls?
在我的 Angular 应用程序中,我有两个控制器,它们都需要访问相同的数据。
为此,我创建了一个服务,负责保存和提供对该数据的访问:
angular.module("SomeModule").factory( "SomeService", function( $http ) {
var svc = {};
var data = {};
// on initialization, load data from the server
$http.get( "somefile.json" )
.success( function( data ) {
svc.data = data;
} );
svc.getItem = function( id ) {
// find the specified item within svc.data, and return its value
};
return svc;
} );
...我已经将该服务注入到两个控制器中的每一个中:
angular.module("SomeModule").controller( "SomeController", function( $routeParams, SomeService ) {
var ctrl = this;
ctrl.item = null; // set an initial value
// load the item that was requested in the URL
ctrl.item = SomeService.getItem( $routeParams.id );
} );
这个几乎 有效——但它有一个很大的缺陷。如果 SomeController
在 SomeService
完成加载 somefile.json
之前调用 SomeService.getItem()
,则 SomeService
将没有任何数据到 return。
实际上,如果我多次加载该应用程序,一些加载将起作用(即、SomeService
将首先完成加载 somefile.json
,控制器将根据需要呈现数据),其他加载则不会(即 、SomeController
将尝试从中检索数据SomeService
在数据真正加载之前,一切都会崩溃和烧毁。
显然,我需要找到一些方法来推迟 getItem()
的执行,直到 SomeService
实际上准备好处理这些调用。但我不确定最好的方法。
我能想到一些相当复杂的解决方案,例如在 SomeService
中构建我自己的调用队列,并连接一堆复杂的回调。但是必须有一个更优雅的解决方案。
我怀疑 Angular's $q service 在这里可能有用。但是,我是 promises 的新手,我不确定我应该如何在这里使用 $q
(甚至我是否在咆哮正确的树)。
你能把我推向正确的方向吗?我将不胜感激。
您还有 2 个不错的选择
使用回调
使用 $q return 承诺
使用回调:
svc.getItem = function( id,callback ) {
$http.get( "somefile.json" )
.success( function( data ) {
svc.data = data;
callback(svc.data)
} );
};
在控制器中
SomeService.getItem( $routeParams.id,function(data){
ctrl.item = data
} );
使用承诺:
svc.getItem = function( id) {
var deferred = $q.defer();
$http.get( "somefile.json" )
.success( function( data ) {
svc.data = data;
deferred.resolve(svc.data);
} )
.error(function (error) {
deferred.reject(error);
});
return deferred.promise;
;
};
在控制器中
SomeService.getItem( $routeParams.id).then(function (data) {
ctrl.item = data
},
function (error) {
//do something with error
});
我们是这样做的,我们使用 $q 来延迟异步调用将提供您的服务的任何内容,然后我获取响应的数据部分并解析它,它将所需的数据发送到控制器(没有状态, headers...).
我在我的服务中使用 try catch 语句,以使错误处理远离控制器。
angular.module("JobsService", [])
.factory("JobsService", ['$q', '$http', '$log', function ($q, $http, $log) {
var serviceName = "JobsService";
var url = "http://localhost:8080/path/to/resource/";
var service = {};
service.getAll = function () {
var deferred = $q.defer();
try {
$http.get(url + "/jobs")
.success(function (response, status) {
$log.debug("GET response in " + serviceName + " returned with status " + status);
deferred.resolve(response);
})
.error(function (error, status) {
deferred.reject(error + " : " + status);
});
} catch (err) {
$log.error(err);
deferred.reject();
}
return deferred.promise;
};
return service;
}]);
然后在控制器中
JobsService.getAll()
.then(function (response) {
$scope.jobs = response;
// records are stored in $scope.jobs
}, function (response) {
$scope.jobs = undefined;
})
.finally(function () {
// will always run
});
这是我在自己的项目中的做法。
您的服务
angular.module("SomeModule").factory( "SomeService", function( $http ) {
var svc = {};
svc.data = {};
// on initialization, load data from the server
svc.getData = function(){
return $http.get( "somefile.json" );
};
return svc;
} );
您的控制器
angular.module("SomeModule").controller( "SomeController", function( $routeParams, SomeService ) {
ctrl.items = null; // set an initial value
// load the item that was requested in the URL
SomeService.getData().success(function(data){
ctrl.items = data;
}).error(function(response){
console.err("damn");
});
} );
要点:承诺
依我愚见,处理异步调用的责任在于控制器。只要有可能,我总是 return $http 承诺。
svc.getData = function(){
return $http.get( "somefile.json" );
};
您可以在服务中添加一些逻辑,但您始终必须 return 承诺。 (要知道:.success() 承诺 return 承诺)
控制器将具有根据异步调用的响应知道如何行为的逻辑。他必须知道在成功和错误的情况下如何表现。
如果您有更多问题,请随时提问。希望对你有帮助。
我建议更好地利用 AngularJS 的路由功能,它允许您解决依赖关系,以及 $http 服务缓存,并相应地构建您的应用程序。
因此,我认为您需要完全摆脱您的服务。
从下面的例子开始,taken straight from the Angular documentation:
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
因此 PhoneListCtrl 和 PhoneDetailCtrl 都需要来自 somefile.json 的数据。我会像这样将数据注入每个控制器:
(function(){
angular.module('phonecatApp').controller('PhoneListCtrl', ['somefileJsonData', function(somefileJsonData){
this.someFileJsonData = someFileJsonData;
}]);
})();
与 PhoneDetailCtrl 相同的想法。
然后像这样更新您的路由:
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl',
resolve:{ somefileJsonData: ['$http',function($http){
return $http.get("somefile.json", { cache: true });
}] }
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl',
//same resolve
}).
otherwise({
redirectTo: '/phones'
});
}]);
这样,您就让 angular 在路由过程中负责解决此依赖关系。
将 cache 设置为 true 也会对其进行缓存,这样您就不会两次执行相同的 Get 请求,并且 Angular 只会在依赖项为已解决。
因此,在您的应用中,SomeController 与视图配对作为路由过程的一部分,使用 resolve 来解析 item,并将其注入控制器。
试试这个代码
angular.module("SomeModule").factory("SomeService", function ($http) {
var svc = {};
svc.getList = function () {
return $http.get("somefile.json");
};
svc.getItem = function (id) {
svc.getList().then(function (response) {
// find the specified item within response, and return its value
});
};
return svc;
});
在我的 Angular 应用程序中,我有两个控制器,它们都需要访问相同的数据。
为此,我创建了一个服务,负责保存和提供对该数据的访问:
angular.module("SomeModule").factory( "SomeService", function( $http ) {
var svc = {};
var data = {};
// on initialization, load data from the server
$http.get( "somefile.json" )
.success( function( data ) {
svc.data = data;
} );
svc.getItem = function( id ) {
// find the specified item within svc.data, and return its value
};
return svc;
} );
...我已经将该服务注入到两个控制器中的每一个中:
angular.module("SomeModule").controller( "SomeController", function( $routeParams, SomeService ) {
var ctrl = this;
ctrl.item = null; // set an initial value
// load the item that was requested in the URL
ctrl.item = SomeService.getItem( $routeParams.id );
} );
这个几乎 有效——但它有一个很大的缺陷。如果 SomeController
在 SomeService
完成加载 somefile.json
之前调用 SomeService.getItem()
,则 SomeService
将没有任何数据到 return。
实际上,如果我多次加载该应用程序,一些加载将起作用(即、SomeService
将首先完成加载 somefile.json
,控制器将根据需要呈现数据),其他加载则不会(即 、SomeController
将尝试从中检索数据SomeService
在数据真正加载之前,一切都会崩溃和烧毁。
显然,我需要找到一些方法来推迟 getItem()
的执行,直到 SomeService
实际上准备好处理这些调用。但我不确定最好的方法。
我能想到一些相当复杂的解决方案,例如在 SomeService
中构建我自己的调用队列,并连接一堆复杂的回调。但是必须有一个更优雅的解决方案。
我怀疑 Angular's $q service 在这里可能有用。但是,我是 promises 的新手,我不确定我应该如何在这里使用 $q
(甚至我是否在咆哮正确的树)。
你能把我推向正确的方向吗?我将不胜感激。
您还有 2 个不错的选择
使用回调
使用 $q return 承诺
使用回调:
svc.getItem = function( id,callback ) {
$http.get( "somefile.json" )
.success( function( data ) {
svc.data = data;
callback(svc.data)
} );
};
在控制器中
SomeService.getItem( $routeParams.id,function(data){
ctrl.item = data
} );
使用承诺:
svc.getItem = function( id) {
var deferred = $q.defer();
$http.get( "somefile.json" )
.success( function( data ) {
svc.data = data;
deferred.resolve(svc.data);
} )
.error(function (error) {
deferred.reject(error);
});
return deferred.promise;
;
};
在控制器中
SomeService.getItem( $routeParams.id).then(function (data) {
ctrl.item = data
},
function (error) {
//do something with error
});
我们是这样做的,我们使用 $q 来延迟异步调用将提供您的服务的任何内容,然后我获取响应的数据部分并解析它,它将所需的数据发送到控制器(没有状态, headers...).
我在我的服务中使用 try catch 语句,以使错误处理远离控制器。
angular.module("JobsService", [])
.factory("JobsService", ['$q', '$http', '$log', function ($q, $http, $log) {
var serviceName = "JobsService";
var url = "http://localhost:8080/path/to/resource/";
var service = {};
service.getAll = function () {
var deferred = $q.defer();
try {
$http.get(url + "/jobs")
.success(function (response, status) {
$log.debug("GET response in " + serviceName + " returned with status " + status);
deferred.resolve(response);
})
.error(function (error, status) {
deferred.reject(error + " : " + status);
});
} catch (err) {
$log.error(err);
deferred.reject();
}
return deferred.promise;
};
return service;
}]);
然后在控制器中
JobsService.getAll()
.then(function (response) {
$scope.jobs = response;
// records are stored in $scope.jobs
}, function (response) {
$scope.jobs = undefined;
})
.finally(function () {
// will always run
});
这是我在自己的项目中的做法。
您的服务
angular.module("SomeModule").factory( "SomeService", function( $http ) {
var svc = {};
svc.data = {};
// on initialization, load data from the server
svc.getData = function(){
return $http.get( "somefile.json" );
};
return svc;
} );
您的控制器
angular.module("SomeModule").controller( "SomeController", function( $routeParams, SomeService ) {
ctrl.items = null; // set an initial value
// load the item that was requested in the URL
SomeService.getData().success(function(data){
ctrl.items = data;
}).error(function(response){
console.err("damn");
});
} );
要点:承诺
依我愚见,处理异步调用的责任在于控制器。只要有可能,我总是 return $http 承诺。
svc.getData = function(){
return $http.get( "somefile.json" );
};
您可以在服务中添加一些逻辑,但您始终必须 return 承诺。 (要知道:.success() 承诺 return 承诺)
控制器将具有根据异步调用的响应知道如何行为的逻辑。他必须知道在成功和错误的情况下如何表现。
如果您有更多问题,请随时提问。希望对你有帮助。
我建议更好地利用 AngularJS 的路由功能,它允许您解决依赖关系,以及 $http 服务缓存,并相应地构建您的应用程序。
因此,我认为您需要完全摆脱您的服务。
从下面的例子开始,taken straight from the Angular documentation:
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
因此 PhoneListCtrl 和 PhoneDetailCtrl 都需要来自 somefile.json 的数据。我会像这样将数据注入每个控制器:
(function(){
angular.module('phonecatApp').controller('PhoneListCtrl', ['somefileJsonData', function(somefileJsonData){
this.someFileJsonData = someFileJsonData;
}]);
})();
与 PhoneDetailCtrl 相同的想法。
然后像这样更新您的路由:
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl',
resolve:{ somefileJsonData: ['$http',function($http){
return $http.get("somefile.json", { cache: true });
}] }
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl',
//same resolve
}).
otherwise({
redirectTo: '/phones'
});
}]);
这样,您就让 angular 在路由过程中负责解决此依赖关系。
将 cache 设置为 true 也会对其进行缓存,这样您就不会两次执行相同的 Get 请求,并且 Angular 只会在依赖项为已解决。
因此,在您的应用中,SomeController 与视图配对作为路由过程的一部分,使用 resolve 来解析 item,并将其注入控制器。
试试这个代码
angular.module("SomeModule").factory("SomeService", function ($http) {
var svc = {};
svc.getList = function () {
return $http.get("somefile.json");
};
svc.getItem = function (id) {
svc.getList().then(function (response) {
// find the specified item within response, and return its value
});
};
return svc;
});