我们另一个系统是把angular用在单页面应用里,对angular做了一些定制,集成了seajs,没有用angular自己的模块管理。不过angular单独使用也是可以的,新开发的一个较小的系统,就没有使用前端js的模块管理,而是简单集成了ejs和angular,本文是一个简单的demo
流程
首先是express的一个服务,用于跳转到ejs。该服务类似于struts2里的action类:
function myAccount(req, res, next){ res.render("binding_list", {layout:"storeadmin_layout", menu:"myAccount"}); }
通过http client发送GET请求到地址/svc/weixin/myAccount,就会调用该服务,该服务在服务端准备数据之后,调用express的render函数,跳转到ejs,下面是ejs的代码。.ejs相当于jsp文件,都是一个服务端模板
<%- Loader().js('/weixin/weixin.js').done(config) %> <div ng-controller="WeixinController"> <span ng-if="hasBinding">已绑定</span> <span ng-if="!hasBinding">未绑定</span> </div>
在.ejs里可以写javascript代码,经过ejs引擎渲染之后,生成html页面到客户端,response body是:
<script src="/weixin/weixin.js?v=1407754967801"></script> <div ng-controller="WeixinController"> <span ng-if="hasBinding">已绑定</span> <span ng-if="!hasBinding">未绑定</span> </div>
上面这段html片段,里面已经包含了angular标签,但是angular还没有在客户端(浏览器里)执行。然后浏览器解析到<script>标签,又去请求加载weixin.js文件,这个文件里放的才是angular的代码:
function WeixinController($scope){ $scope.name = "kyfxbl"; $.get("/svc/weixin/checkBinding", function(res){ $scope.hasBinding = res.flag; $scope.$digest(); }); }
这时候,浏览器已经加载了angular.js和weixin.js,这2个文件里的代码,都是在浏览器里执行的,和服务端的express和ejs已经没有关系了 。后面就是angular在起作用,对html文件再次编译,得到的就是最终呈现给用户的界面
这个过程分为2个阶段,第一个阶段跑在node里,主要是ejs引擎起作用,把.ejs文件渲染成html文件,发响应给回浏览器。第二个阶段跑在浏览器里,主要是angular起作用,把html里的变量,替换为$scope中的模型。所以这里有2次变量替换的过程,在服务端是把<%= %>替换成render函数传递的模型;在客户端是把{{ }}替换成&scope上挂载的模型
server端传递变量
主要容易混淆的地方在于,如果想在server端把值写到weixin.js里该怎么做
比如说,在.ejs里有这么一个表达式:
{{ name }}
name是在浏览器里由angular编译替换的,所以weixin.js里,就需要有这样的代码:
$scope.name = "kyfxbl";
但是如果name是需要node从数据库中读出来的,那么就有2种做法:
一种是把js嵌入到.ejs中(就不需要单独的weixin.js了):
<script> function WeixinController($scope){ $scope.name = "<%= name %>"; } </script> <div ng-controller="WeixinController"> {{ name }} </div>
但是这种方式我并不推荐,主要是有2个问题:
1、把js代码直接嵌在html里,这种做法很不好。因为复杂一点的页面,<script>里的内容会变得很长,很难维护。而且js压缩工具,也不好处理这种情况。最佳实践是把js和html分离开
2、render传过来的name变量,还需要手工地加上"",否则的话就会报错。<script>里到处都需要这么处理,非常容易出错,定位起来也很麻烦
所以最好是采用第二种方式,还是把.ejs和weixin.js分离开,weixin.js里初始化angular controller的时候,再次去服务端请求需要的变量:
function WeixinController($scope){ $scope.name = "kyfxbl"; $.get("/svc/weixin/checkBinding", function(res){ $scope.hasBinding = res.flag; $scope.$digest(); }); }
这样做的坏处是http的请求数会变得比较多(第一种方式可以在接受请求的时候,把需要的变量都准备好写到.ejs里),不过可以通过合并接口等方式,减少http请求的次数。我认为也好过把js代码嵌在html里