AngularJS入门教程(三)MVC模式和服务

HTML5 herman 1551浏览 0评论
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

在上一篇文章中,我们主要学习了AngularJS的生命周期和MVC模式的使用,以及常见的ng-click指令、ng-controller指令、ng-model指令。和入门实战,本章我们将继续学习AngularJS的数据双向绑定、服务(Services)等知识。

AngularJS的数据双向绑定

在MVC一节中,通过几个示例介绍了如何创建和使用Controller、Model,如何与View层交互,其实也引出了AngularJS的一个重要特性。在Controller小节的例子中,通过点击按钮由Controller更改数据模型并将其展示在页面中,这是通过数据模型的变化从而影响视图层的显示。在Model小节的例子中,通过修改输入框中的值,由Controller捕获并更新对应的数据模型,这是通过视图层的变化从而影响数据模型的值。这就是AngularJS的数据双向绑定特性。

AngularJS的服务(Services)

AngularJS中一个重要的概念是服务,这个服务的概念比较宽泛,比如一个常量值也算做一个服务,既提供一个不可变值的服务。变量、对象、函数都算做是服务。在AngularJS中内置了好几十个服务,这些内置的服务都以$符号开头,比如$scope、$http、$log、$timeout、$interval等等,从字面意思都不难理解它们的作用,更多的内置服务可以去AngularJS官网查看API文档。

AngularJS的服务特征

AngularJS中的服务有两个主要特点: 

  1. 延迟加载,当应用中的其他组建使用服务时才会实例化。 
  2. 单例,在应用的整个生命周期中,一个服务只存在一份实例,所以服务一般用来共享可复用的代码逻辑或者数据。

AngularJS的自定义服务

除了内置的服务,我们还可以创建自己的服务,在AngularJS中我们可以通过$provide这个内置的服务来创建我们的自定义服务,$provide服务提供了五个方法供我们创建不同应用场景的自定义服务,这五个方法分别是provider(name, provider)、factory(name, $getFn)、service(name, constructor)、value(name, value)、constant(name, value)。

Value

我们先从value(name, value)这个方法看起,该方法有两个参数: 

  1. 第一个参数为服务的名称,类型为字符串。 
  2. 第二个参数可以是字符串、数字、数组、对象或者函数。

假设在我们的应用中,多个Controller中都使用了相同的属性,比如都需要用到客户端ID这个属性,那么我们可以将其抽象为一个服务,该服务就专门用来获取客户端ID,来看看如何创建这个服务:

var mainModule = angular.module("mainModule", []);
mainModule.value("clientId", "qazxsw123456");

上面的示例代码创建了名为clientId的服务,该服务其实就是一个字符串。不过这和$provide服务有什么关系呢?其实上面这种写法并不是完整的写法,只是一个语法糖而已,真正完整的写法是在模块的.config()方法中通过$provide服务去创建:

mainModule.config(function($provide) {
    $provide.value("clientId", "qazxsw123456");
});

创建好服务后通过AngularJS的注入机制将其注入到Controller中:

mainModule.controller("FirstController", ["$scope", "clientId", function($scope, clientId) {
  $scope.clientId = clientId;
}]);
mainModule.controller("SecondController", ["$scope", "clientId", function($scope, clientId) {
  $scope.clientId = clientId;
}]);

然后在HTML页面中正常使用Controller就可以了:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Demo for Value Service</title>
    <script src="../angular-1.5.8.js"></script>
    <script src="modules.js"></script>
  </head>
  <body ng-app="mainModule">
    <div ng-controller="FirstController">
      Client ID in FirstController: {{ clientId }}
    </div>
    <div ng-controller="SecondController">
      Client ID in SecondController: {{ clientId }}
    </div>
  </body>
</html>

上文中说过$scope服务的其中一个作用就是给Controller添加属性和方法,然后可以在绑定Controller的DOM中使用{{}}语法直接访问添加的属性或调用方法。然而就$scope服务的这一功能而言,AngularJS还提供了另一种方式,我们先来看看Controller的写法:

mainModule.controller("FirstController", ["clientId", function(clientId) {
  this.clientId = clientId;
}]);
mainModule.controller("SecondController", ["clientId", function(clientId) {
  this.clientId = clientId;
}]);

上述代码中我们并没有将$scope服务注入到这两个Controller中,而是使用this创建了clientId属性,this代表Controller的实例。使用这种方式后在HTML页面中使用Controller也有点变化:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Demo for Value Service</title>
    <script src="../angular-1.5.8.js"></script>
    <script src="modules.js"></script>
  </head>
  <body ng-app="mainModule">
    <div ng-controller="FirstController as first">
      Client ID in FirstController: {{ first.clientId }}
    </div>
    <div ng-controller="SecondController as second">
      Client ID in SecondController: {{ second.clientId }}
    </div>
  </body>
</html>

我们看到在ng-conroller标签中不再是直接写Controller名称了,而是使用as关键字声明了Controller的实例,然后在{{}}中使用Controller的实例去访问属性或者调用方法。

使用$scope服务和this给Controller添加属性或方法的效果是一样的,所以不存在谁好谁坏的概念,只不过使用this的方式更贴合OO的思想,而且在HTML代码中对使用的属性或方法有更直观的可读性,能一眼看到使用了哪个Controller的属性或方法,所以使用哪种方式按个人喜好,但是不建议混用这两种方式。

这里在介绍另外一个语法糖,那就是在注入服务的时候不用繁复的在数组中和函数参数中都声明,只需要在函数的参数里声明就可以了:

mainModule.controller("FirstController", function($scope, clientId) {
  $scope.clientId = clientId;
});
// 或者
mainModule.controller("FirstController", function(clientId) {
  this.clientId = clientId;
});

Constant

我们再来看看constant(name, value)方法:

var mainModule = angular.module("mainModule", []);
mainModule.constant("clientId", "qazxsw123456");

该方法和value(name, value)在创建的服务内容形式上来说是一样的,但是两者创建的服务在功能性上还是有区别的: 

  1. 从名称就可以看出用constant(name, value)方法创建的服务是不可修改的。 
  2. 使用constant(name, value)创建的服务可以在模块的.config()方法中注入,也就是可以在创建其他服务时使用,而使用value(name, value)创建的服务不可以。

Service

现在又有一个需求,希望能获取到当前时间添加在客户端ID后面,那么我们可以使用service(name, constructor)方法来创建获取当前时间的服务:

var mainModule = angular.module("mainModule", []);
mainModule.value("clientId", "qazxsw123456");
mainModule.service("currentDate", Date);
mainModule.controller("FirstController", function(clientId, currentDate) {
  this.clientId = clientId + "-" + currentDate;
});
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Demo for Service Service</title>
    <script src="../angular-1.5.8.js"></script>
    <script src="modules.js"></script>
  </head>
  <body ng-app="mainModule">
    <div ng-controller="FirstController as first">
      Client ID in FirstController: {{ first.clientId }}
    </div>
  </body>
</html>

输出结果: 
Client ID in FirstController: qazxsw123456-Thu Sep 08 2016 17:05:30 GMT+0800 (CST)
service(name, constructor)方法的第二个参数是函数构造器,也就是函数的实例,所以currentDate服务的实体其实就是new Date()。

Factory

现在,我们希望通过一个服务就可以完成客户端ID和当前时间的拼接,不需要给Controller注入两个服务,来看看如何用factory(name, $getFn)方法来实现:

var mainModule = angular.module("mainModule", []);
mainModule.constant("clientId", "qazxsw123456");
mainModule.factory("clientIdAndCurrentDate", function(clientId) {
  return clientId + "-" + new Date();
});
mainModule.controller("FirstController", function(clientIdAndCurrentDate) {
  this.clientId = clientIdAndCurrentDate;
});

首先我们需要用constant(name, value)方法创建clientId服务,因为需要将它注入到新的服务中,前文也介绍过constant(name, value)和value(name, value)方法的区别。然后使用factory(name, $getFn)方法创建clientIdAndCurrentDate服务,该函数的第二个参数类型是函数,我们在该函数中将clientId服务返回的客户端ID与Date构造器返回的时间进行拼接然后返回,当然运行结果还是一样的: 

Client ID in FirstController: qazxsw123456-Thu Sep 08 2016 17:05:30 GMT+0800 (CST)

其实这个服务还可以写成这样:

mainModule.constant("clientId", "qazxsw123456");
mainModule.service("currentDate", Date);
mainModule.factory("clientIdAndCurrentDate", function(clientId, currentDate) {
  return clientId + "-" + currentDate;
});

Povider

现在又有新的需求,希望对clientId后面的时间进行格式化,但假设我们没有权限去更改clientIdAndCurrentDate服务,那么这时我们需要使用provider(name, provider)方法创建另外一个服务,然后对clientIdAndCurrentDate服务进行配置,来看看如何实现这个服务:

var mainModule = angular.module("mainModule", []);
mainModule.constant("clientId", "qazxsw123456");
mainModule.service("currentDate", Date);
mainModule.factory("clientIdAndCurrentDate", function(clientId, currentDate) {
  return clientId + "-" + currentDate;
});
mainModule.provider("clientIdAndCurrentDateByFormat", function() {
  this.formatFunc = function(str) {
    var clientId = str.substring(0, str.indexOf("-"));
    var dateStr = str.substring(str.indexOf("-"), str.length);
    var dateObj = new Date(dateStr);
    var year = dateObj.getFullYear().toString();
    var month = (dateObj.getMonth() + 1).toString();
    var day = dateObj.getDate().toString();
    var hour = dateObj.getHours().toString();
    var minute = dateObj.getMinutes().toString();
    var second = dateObj.getSeconds().toString();
    return clientId + "-" + [year, (month >= 10 ? month : 0 + month), (day > 10 ? day : 0 + day), hour, minute, second].join("");
  };
  this.$get = function(clientIdAndCurrentDate) {
    return this.formatFunc(clientIdAndCurrentDate);
  };
});
mainModule.controller("FirstController", function(clientIdAndCurrentDateByFormat) {
  this.clientId = clientIdAndCurrentDateByFormat;
});

首先我们创建了formatFunc()辅助配置函数,然后实现了$get方法,通过formatFunc()辅助函数配置clientIdAndCurrentDate服务,我们来看运行结果: 

Client ID in FirstController: qazxsw123456-20160909113523

要注意的一点是,通过provider(name, provider)方法创建服务时必须要显式的实现$get方法,并且只有在$get方法中才能注入其他服务。在AngularJS中服务仅指$get返回的东西,所以前四种创建服务的方法其实都是provider(name, provider)方法根据不同应用场景实现的语法糖,比如factory方法其实就是把一个函数当作了$get方法,service方法其实是将一个函数构造方法或者说函数实例当作了$get方法,value和constant方法其实又是对factory方法的语法糖实现。所以在自定义服务时可按需选择不同的方法创建服务。

版权声明:本文为博主原创文章,未经博主允许不得转载。

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » AngularJS入门教程(三)MVC模式和服务