AngularJS教程之三 服务与依赖注入

上页

  在第一章里,我们知道Angular提供的标准服务组件有以下:

  • $http:用于处理 XMLHttpRequest
  • $location:提供当前URL的信息
  • $q: 异步请求使用,promise/deferred模块
  • $routeProvider:配置路由
  • $log:日志服务

我们看看最常用和调用后端的$http的用法:

$http有下面短方法:$http.get() $http.head() $http.post() $http.put() $http.delete() $http.jsonp()

我们假设调用后端的REST URL是app/phones/phones.json,其返回的是一个Json:

[
 {
  "age": 13,
  "id": "motorola-defy-with-motoblur",
  "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
  "snippet": "Are you ready for everything life throws your way?"
  ...
 },
...
]

AngularJS的所有Service服务组件都是由依赖注入管理的,DI能够分离表现 数据和控制器,实现分离关注和松耦合。

控制器的代码是:

var phonecatApp = angular.module('phonecatApp', []);
 
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
  $http.get('phones/phones.json').success(function(data) {
    $scope.phones = data;
  });
 
  $scope.orderProp = 'age';
});

注意到:

phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...}

这表示创建一个名称为'PhoneListCtrl'的服务,其内容是function ($scope, $http) {...},也就是类似

var PhoneListCtrl =function ($scope, $http) {...}

Angular将PhoneListCtrl注入到控制器中,当然PhoneListCtrl也依赖于$scope和$http,它们也是一样,都是被注入器注入。

$http 实现对后端Web服务器的GET请求,URL是: phone/phones.json,路径是相对于当前 index.html文件

当$http成功后success 方法返回一个promise($q)对象。参考:结合RxJS + AngularJS实现异步处理

我们可以使用返回的数据Data赋值到当前的作用域(胶水)$scope中。angular会侦测到JSON响应,然后转换成类似数组的格式。

创建自己的服务

  虽然AngularJS提过了很多有用的服务,但是如果你要创建一个很棒的应用,你可能还是要写自己的服务。你可以通过在模块Module api中注册一个服务工厂函数,或者通过Modeul#factory api或者直接通过模块配置函数中的$provide api来实现。

  所有的服务都符合依赖注入的原则。它们用一个唯一的名字将自己注册进AngularJS的依赖注入系统(injector),并且声明需要提供给工厂函数的依赖。

使用angular.Module api注册服务:

var myModule = angular.module('myModule', []);
myModule.factory('serviceId', function() {
  var shinyNewServiceInstance;
  //factory function body that constructs shinyNewServiceInstance
  return shinyNewServiceInstance;
});

使用$provide服务:

angular.module('myModule', [], function($provide) {
  $provide.factory('serviceId', function() {
    var shinyNewServiceInstance;
    //factory function body that constructs shinyNewServiceInstance
    return shinyNewServiceInstance;
  });
});

$的命名约定

  前缀$是表示 Angular自己提供的服务名称,如$scope或$provide等,为了防止冲突,最好避免命名自己开发的服务以为$开头。

  如果你检查一个scope内部,你也可能会发现一些属性开头也是以 $开头。这些特性被认为是私有的,并且不应该访问或修改。

下面这个代码是将$window注入到自己的服务中:

angular.module('myModule', [], function($provide) {
  $provide.factory('notify', ['$window', function(win) {
    var msgs = [];
    return function(msg) {
      msgs.push(msg);
      if (msgs.length == 3) {
        win.alert(msgs.join("\n"));
        msgs = [];
      }
    };
  }]);
});

这是一个通知服务,将消息发送到所有Angular提供的window窗口中显示。

  要注意的是所有AngularJS服务都是单例的。这意味着在每一个注入器中都只有一个需要的服务的实例。因为AngularJS极度讨厌全局的东西,这是符合面向对象OO。

我们经常会使用压缩简写对javascript进行缩小,以便获取更小的文件更快下载,压缩简写minfy可能对AngularJS注入有影响。用以下办法:

function PhoneListCtrl($scope, $http) {...}
PhoneListCtrl.$inject = ['$scope', '$http'];
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);

使用PhoneListCtrl.$inject 主动注入两个依赖。或者用括号符号[ ]

function PhoneListCtrl($scope, $http) {...}
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);

这样之前案例控制器代码就写成了:

var phonecatApp = angular.module('phonecatApp', []);
 
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',
  function ($scope, $http) {
    $http.get('phones/phones.json').success(function(data) {
      $scope.phones = data;
    });
 
    $scope.orderProp = 'age';
  }]);

$resource服务与REST

  $resource是一个依赖$Http的服务组件,它创建了一个资源对象,让你与RESTful服务器端数据源实现交互的工厂。返回的是资源对象,提供了高层次的行为,而不需要与低级别$ HTTP服务交互操作方法。需要ngResource 安装(<script src="lib/angular/angular-resource.js"></script>)。具体用法见API

对返回的数据进行默认的如下操作:

{ 'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'} };

例如:

var User = $resource('/user/:userId', {userId:'@id'});
var user = User.get({userId:123}, function() {
user.abc = true;
user.$save();
});

  User定义为资源$resource类型,小写的user是其一个实例,实际是从服务器抓取的根据id为123的一个User Json数组,那么我们下面可以对user这个实例使用上面几个默认操作,比如user.$save();。可以轻松地执行CRUD操作(创建,读取,更新,删除)。

总结为:

var object = Data.get({id:123}, function() {
object.isDefault = true;
object.$save();
});

下面以phone举例返回列表:

var phonecatServices = angular.module('phonecatServices', ['ngResource']);
 
phonecatServices.factory('Phone', ['$resource',
  function($resource){
    return $resource('phones/:phoneId.json', {}, {
      query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
    });
  }]);

这是从后台返回phone列表的$resource用法。节省了$http之类转换。

下页

依赖注入

AngularJS专辑

使用Angular2建立一个可扩展单页应用