最近公司在搞新项目,web端采用的是angularJS ,关于angularJS是什么以及它的基本用法我这里不做过多解释,因为之后如果要写的话,再一点点的去讲,新人接触ng都会被它的一些概念所弄的迷糊,我也是刚接触几天才把ng中的一些概念他们之间的关系搞清楚,这里我可能说的有些不正确,也请见谅并指正。本文主要通过一个实际案例给大家讲清楚关于模块,控制器,服务,指令等他们之间的关系,并完成一个登录的功能。
首先需要说的是angularJS中是一款基于MVC的前端框架,MVC就是控制器,视图,数据模型的概念,在我们用struts的时候,数据模型其实就是我们定义在action中的javabean或者字段,视图就是jsp或者html 而控制器就是我们的action,这样他们各司其职共同完成MVC,同理在angularJS中也做了MVC的分解,这是和jquery相比一个比较大的区别,但是我不认为jquery没有MVC,只是我们平时的开发中并没有类似的开发习惯,jquery没有天然的提供这种开发模式所以我们才忽视了,angular提供了这些天然的支持。在angularJS中,控制器是完成一些基本的获取信息,跳转,调用等工作,在控制器中我们会操作model,操作model就等于操作scope,所谓scope我理解的就是用来存储数据的一个对象,就好比struts我们用的request对象session对象,而applcation对象就等于angular中的rootScope对象。视图就是html,在html中我们会引入一些公用的组件例如表单,列表,下拉框等,这些ui组件我们通常会使用easyui,在angular中,我们会用指令进行封装,当然angular提供了一些内置的指令。
这么说还有一些乱,简单来说就是,用户看到 使用了指令的视图,通过操作指令将数据参数传递给了控制器,控制器从scope中获取相关参数后,调用服务完成具体业务逻辑。
另外还有两个概念我们细说,1是模块,2是服务。 模块我理解的就是一个包,它将一类具体操作(指令,控制器,服务)做了封装。再我们想使用某个指令或者控制器的时候只要引入这个模块(包)就可以了。当然一个模块的定义不只说非要包含这些东西,它可以单独就包含指令,或者就包含控制器,或者就包含服务,例如
<pre name="code" class="javascript">// 定义相关模块
window.routeModule = angular.module('msd.routes',['ngRoute']);
window.serviceModule = angular.module('msd.services', ['ngResource']);;
window.controllerModule = angular.module('msd.controllers', ['msd.services']);
window.diretiveModule = angular.module('msd.directives', ['msd.services', 'msd.controllers']);
window.filterModule = angular.module('msd.filters', []);
window.cookieModule = angular.module('msd.cookies', ['ngCookies']);
我们不讨论他们如何定义是最好的,看具体的项目实施。这些模块之间可以进行依赖和注入。例如在window.diretiveModule = angular.module('msd.directives', ['msd.services', 'msd.controllers']);中就将service和controller注入到了指令模块中。这样在所有指令里都能调用到控制器和服务的相关接口;服务简单来说就是一个函数或者一个接口,它帮助我们处理一些业务逻辑。当然也可以理解为一个工具,因为在angular中提供了很多现成的服务例如最常用到的$http服务,通过$http我们可以使用post和get请求。
以上都是一些基本的常识。具体的细节我们之后再说。下面来看一个登录的demo。
首先我们需要引入一些angular的lib,因为我们的demo很简单,所以就引入了一个js:
<script type="text/javascript" src="../../../lib/angular/angular.js"></script>
除此之外,我们还会将其他的js代码都分门别类到了其他的JS文件里,所以我们也要记得引入(实际开发中我们可能会用到requirJS)这里我们不搞这么复杂。
<script type="text/javascript" src="../controller/controller.js" ></script>
<script type="text/javascript" src="../directive/directive.js" ></script>
<script type="text/javascript" src="../service/loginService.js" ></script>
从引入中我们看到了我们将控制器,指令,服务都做了分解,这样维护起来也简单。整个的目录结构如下:
首先我们来看login.html文件:页面很简单除了引入的一些js文件外,我们只写了一行指令的代码,而这个plus-user-form就是一个我们自定义的指令,它是一个ui组件。
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title>登录</title>
<meta charset="UTF-8">
<script type="text/javascript" src="../../../lib/angular/angular.js"></script>
<script type="text/javascript" src="../controller/controller.js" ></script>
<script type="text/javascript" src="../directive/directive.js" ></script>
<script type="text/javascript" src="../service/loginService.js" ></script>
</head>
<body>
<div>
<plus-user-form></plus-user-form>
</div>
</body>
</html>
在页面里我们还需要注意的是在第二行,我们写了一个ng-app的属性,它的值是myApp,angular就是通过ng-app作为入口的,一旦它发现有ng-app存在,那它就认为这之下的页面全部由angularJS来管理。我们给ng-app赋值了myApp 意味着,这个页面的ui以及业务逻辑处理都属于myApp模块。刚才说了myapp既然是一个模块,那就类似一个包,这个包下包含控制器,服务等。同理也能找到我们定义的这个指令,下面我们来看如下定义一个模块,打开controller,js文件,我们可以看到这样一行代码:
var myApp = angular.module('myApp',[]); //这行代码就是指利用angular提供的module方法创建了一个名为myApp的模块,[]应该是依赖的模块或者服务。
在这个模块里,首先我们来创建一个控制器,创建控制器的代码如下,也是在controller.js文件里:
myApp.controller("loginController",function($scope,$location,$window,LoginService){
$scope.login = function () {
if (!$scope.username) {
console.log("请输入用户名");
return;
}
if (!$scope.password) {
console.log("请输入密码");
return;
}
LoginService.loginDo({
'username': $scope.username,
'password': $scope.password
}).then(function (data) {
if (data == null) {
console.log("登录异常");
} else {
if (200 == data) {
console.log("登录成功");
$window.location.href = "http://www.baidu.com";
} else {
console.log("失败");
$window.location.href = "http://www.damai.cn";
}
}
});
};
});
我们通过controller方法创建了一个名称为loginController的控制器,这个控制器是一个函数,它注入了$scope,$location,$window等内置的服务,另外还注入了LoginService咱们自定义的服务,为什么这里可以引入这个LoginSerivce,因为这个LoginService也属于这个模块,在loginService.js文件里我们创建了这个服务。接下来,我们为$scope创建了一个函数login,这个login就类似于action中的接口一样可以被调用。同理,它也可以定义并一些一些属性。例如$scope.username就是在scope这个作用域中创建的属性。我们通过获取用户名和密码判断它是否为空,如果为空我们会做提示(其实angularJS提供了很多的校验机制和提示指令这里我们不细说了)。如果用户名和密码不为空我们调用了loginService的loginDo函数,将用户名和密码传了过去。在loginDo的内部其实调用的是$http服务去请求后台了。调用$http服务会返回一个promise对象,它有个then方法用来处理回调,在then方法中的data参数代表了本次请求的响应数据。除此之外,它还有success()函数和error函数。这里我们做了简单的判断如果服务器返回200,我们就跳到百度,如果不是200就跳到大麦网。这样我们的控制器就完成了,是不是和struts中的action挺类似的,那下面我们再来看一下loginService是如何定义的,打开LoginService.js文件,代码如下:
var myApp = angular.module('myApp');
myApp.factory("LoginService",function($http,$q){
return {
loginDo: function(httpParams){
var deferred = $q.defer();
$http({
method:"post",
url: "/angular-war/loginServlet",
params:httpParams
}).success(function(data, status) {
deferred.resolve(data);
}).error(function(data, status) {
deferred.reject(null);
});
return deferred.promise;
}
};
});
首先我们通过module方法获取了myapp这个模块,利用angular提供的factory方法创建了一个名为LoginService的服务(创建服务的方式有很多种例如service(),provider()等这里我们不细说他们的区别)这个服务引入了$http服务和$q服务,其中http我们都很熟悉了,$q服务我也不太熟,在我们项目里用到了,百度了一下
通过调用 $q.defer() 可以构建一个新的 deffered 实例。
deffered 对象用来将 Promise 实例与 标记任务状态(执行成功还是不成功)的 API 相关联。
deffered 对象的方法
resolve(value) ——传入 value 解决派生的 promise。 如果 value 是一个通过 $q.reject 构造的拒绝对象(rejection) , 该promise 将被拒绝。
reject(reason) ——拒绝派生的promise,并提供原因 。 这相当于通过 $q.reject构造的拒绝对象(rejection)作为参数传递给 resolve。
notify(value) ——在 promise 执行的过程中提供状态更新。 这在 promise 被解决或拒绝之前可能会被多次调用。
姑且先防着,之后再说。然后我们通过post或者get方法调用了服务器的接口并将结果返回给控制器。
ok,控制器和服务我们都看过了,模块也知道怎么回事了,页面咱们也看了,就剩下指令了,指令directive刚才说了,可以理解为一个 UI组件,本身angularJS提供了一些内置的指令但也提供了自定义指令的API,这样可以让全世界的人民都来封装优秀的指令供大家使用。这里我们只是为了演示所以就随便写了写,打开direcitve.js文件,代码如下:
var myApp = angular.module("myApp");
myApp.directive('plusUserForm', function () {
return {
restrict: "E",
replace: true,
templateUrl: '/angular-war/resource/js/login/html/plusUserForm.html'
};
});
关于如何自定义指令以及指令的相关属性函数都是什么意思,它可以作为一个专题去说,这里我们只是简单的说一下其中restrict代码这个指令以一种什么形式显示在页面里,有4个格式分别是E(element) 元素 A(attribute) 属性 C(class) 类 M(注释) 其中属性是默认的,因为属性的兼容性最好。replace就两个值false或者true 代码返回的template内容是嵌套在指令中还是覆盖指令,templateUrl是指这个指令的具体UI页面。我们这里使用了plusUserForm这个html。下面我们来看一下这个form我们如何定义的,打开plusUserForm.html文件代码如下:
<form name="userForm" method="post" ng-controller="loginController">
<input name="username" type="text" placeholder="请输入用户名" ng-model="username" required />
<input name="password" type="text" placeholder="请输入密码" ng-model="password" required />
<a ng-click="login()">登录</a>
</form>
这个form中几个注意的地方包括我们使用angularJS内容的ng-controller指令,它代表这个form提交后将会提交个这个控制器来处理,而这个loginController我们已经定义好了,在两个文本框中,我们通过ng-model指令绑定了两个属性分别为username和password 这两个属性在控制器的scope中是一一对应的。最后在a标签中我们使用了ng-click这个指令代表一旦我们点击了超链接就会触发login函数,这个login函数是在loginController控制器里定义的。
OK 这样整个流程我们就说了一遍。最后我们看下后台的代码怎么写的,为了方便我写了一个servlet,代码如下:
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("username:"+username+",password:"+password);
PrintWriter printWriter = resp.getWriter();
printWriter.print(200);
printWriter.flush();
printWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
这样所有代码都说完了,我们看下效果,进入主页面
输入用户名和密码后点击登录,页面重定向到了baidu页面,这样我们的demo就做完了。
之后如果有时间我再整理其他的相关知识然后分享给大家。