在示例中,大部分网页文件在Views文件夹下。 Veiws文件夹中,一个模块对应一个子文件夹,如Accounts, Customers, Orders, Products等。 修改用户页面的根路径是 /Views/Customers/CustomerMaintenance, 查询订单页面的根路径是/Views/Orders/OrderInquiry.为了方便控制器动态加载文件,我把这些页面的控制器代码文件也放到Views文件夹下。
修改用户页面的控制器文件路径是 /Views/Customers/CustomerMaintenanceController.js,这样可以简化开发。把公共的代码放到工程的同一个文件夹下,可以让你快速定位需要查看的代码。 在MVC框架里,控制器文件通常被单独放在一个文件夹下,当工程变得比较庞大时,这些文件会难以维护。
渲染HTML模板很容易。 只需设置一下templateUrl属性:
'views/' + rp.section + '/' + rp.tree + '.html'.
引入 rp.setion和 rp.tree变量,可以很容易实现路径匹配、路径转换。转换完路径后,唯一需要做的事是把扩展名 .html连接到字符串末尾。
加载控制器文件的过程有点复杂。 AngularJS路径配置的控制器属性只支持静态的字符串。 它不支持含有变量的字符串,如下:
controller = "Views/" + parentPath + "/" + controllerName + "Controller";
AngularJS 还需要更多的创新。
经过一段时间的研究,我发现可以通过功能分解来设置控制器属性。 结合使用AngularJS的location service和deferred promise特性,我最终实现动态加载js控制器文件时设置控制器属性值。 js性能的一个提升意味着这次改造产生了最终的价值。
路由表里最终只有两个主路径,AngularJS需要对其进行匹配。第二个路径
/:section/:tree/:id
是用来处理那些带有参数的路径的。现在,不管应用变得多大,路由表都将会保持的很小,而且只需要跟两个路径进行匹配,这样就提高了路由匹配的效率。
最终,application-configuration.js使用angularAMD来引导AngularJS应用。
客户管理页面 - 创建和编辑客户信息单页应用中的页面与asp.net页面类似。 相同之处,两者都是html的一个子集。 对于asp.net,当浏览器开始渲染页面元素时,html、js、数据被传入控制层代码,然后,浏览器进行计算、展示。在单页应用中,RequireJS使用ng-view指令把页面内容注入到一个div标签中。
页面初始化时,浏览器通常只渲染html代码。 若在单页应用中使用RequireJS,js会被动态加载。 当页面加载完,浏览器以ajax异步调用的方式从服务器读取数据。
构建于ASP.NET母版页之上的SPA应用程序及其内容内面,你将可以马上收获的性能之一,就死SPA的内容将会被缓存到客户端,而每一个页面都会从服务器端获取到. 使用你拿手的浏览器开发工具,就可以看到内容已经被缓存了。最终你所有的页面都会被缓存,而最后你只是通过AJAX请求通过网络获取服务器段数据而已. 所有这些都促成了高效的响应时间已经增强的用户体验.
<!-- CustomerMaintenance.html -->
<div ng-controller="customerMaintenanceController" ng-init="initializeController()">
<h3> Customer Maintenance </h3>
<table>
<tr>
<td> <label>Customer Code: </label> </td>
<td>
<div ng-bind="CustomerCode" ng-show="DisplayMode"> </div>
<div ng-show="EditMode">
<input ng-model="CustomerCode" type="text"
ng-class="{'validation-error': CustomerCodeInputError}" />
</div>
</td>
</tr>
<tr>
<td> <label>Company Name: </label> </td>
<td>
<div ng-bind="CompanyName" ng-show="DisplayMode"> </div>
<div ng-show="EditMode">
<input ng-model="CompanyName" type="text"
ng-class="{'validation-error': CompanyNameInputError}" />
</div>
</td>
</tr>
<tr>
<td> <label>Address: </label> </td>
<td>
<div ng-bind="Address" ng-show="DisplayMode"> </div>
<div ng-show="EditMode">
<input ng-model="Address" type="text" />
</div>
</td>
</tr>
<tr>
<td> <label>City: </label> </td>
<td>
<div ng-bind="City" ng-show="DisplayMode"> </div>
<div ng-show="EditMode">
<input ng-model="City" type="text" />
</div>
</td>
</tr>
<tr>
<td> <label>Region: </label> </td>
<td>
<div ng-bind="Region" ng-show="DisplayMode"> </div>
<div ng-show="EditMode">
<input ng-model="Region" type="text" />
</div>
</td>
</tr>
<tr>
<td> <label>Postal Code: </label> </td>
<td>
<div ng-bind="PostalCode" ng-show="DisplayMode"> </div>
<div ng-show="EditMode">
<input ng-model="PostalCode" type="text" />
</div>
</td>
</tr>
<tr>
<td> <label>Country: </label> </td>
<td>
<div ng-bind="CountryCode" ng-show="DisplayMode"> </div>
<div ng-show="EditMode">
<input ng-model="CountryCode" type="text" />
</div>
</td>
</tr>
<tr>
<td> <label>Phone Number: </label> </td>
<td>
<div ng-bind="PhoneNumber" ng-show="DisplayMode"> </div>
<div ng-show="EditMode">
<input ng-model="PhoneNumber" type="text" />
</div>
</td>
</tr>
<tr>
<td> <label>Web Site URL: </label> </td>
<td>
<div ng-bind="WebSiteURL" ng-show="DisplayMode"> </div>
<div ng-show="EditMode">
<input ng-model="WebSiteURL" type="text" />
</div>
</td>
</tr>
</table>
<span ng-show="ShowCreateButton">
<button ng-click="createCustomer()">Create </button> </span>
<span ng-show="ShowEditButton">
<button ng-click="editCustomer()">Edit </button> </span>
<span ng-show="ShowUpdateButton">
<button ng-click="updateCustomer()">Update </button> </span>
<span ng-show="ShowCancelButton">
<button ng-click="cancelChanges()">Cancel </button> </span>
<div>
<alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">
<div ng-bind-html="MessageBox"> </div> </alert>
</div>
</div>
查看上面用于示例程序的顾客维护页面的HTML内容,你能够看到其实你可以创建出一个看起来很清晰,也容易阅读的HTML。内容里面也没有引用任何JavaScript.
借助于data-binding指令,AngularJS提供了内容视图及内容控制器之间清晰的关注点分离. 对于输入控制,双向数据绑定通过ng-bind这个指令以及顾客维护控制器的$scope属性得到了实现. AngularJS中的数据绑定功能同其它的JavaScript库,诸如KnockoutJS,功能相似, 对于文档对象模型的转换需求已经成为过去式——这是好事,因为许多的JavaScript问题都源于DOM的转换.
ng-show 指令是的显示隐藏的HTML内容变得容器. 对于顾客维护页面来说,这将会让页面只用设置一个JavaScript的AngularJS $scope变量,就可以同时支持编辑模式和只读模式. ng-click 指令将会执行在按下按钮时执行的控制器函数.
顾客维护控制器示例中的每一个控制器都会被封装到一个RequireJS定义语句中,帮助AngularJS对控制器进行注册. 此外,定义语句将告知RequireJS顾客维护控制器正常运行所依赖的其它库和服务. 在本例中,控制器依赖于 application-configuration,customersService 以及 alertsServices 这些功能. 这些JavaScript依赖将会通过RequireJS被动态加载进来.
AngularJS 使用了依赖注入, 因此控制器所需的所有东西都会通过参数被注入到其中. 如果你希望使用一种单元测试工具,比如Jasmine,来在你的JavaScript控制器上进行单元测试的话,这就会很有用.
$scope 对象提供了视图和控制器之间的双向数据绑定. 控制器里面再也不需要对于HTML内容的直接引用了. 控制器通��执行initializeContent函数启动,这个函数是借助内容页面中的ng-init指令被初始化的 .
顾客维护页面将引用 $routeParams 服务来决定是否传入了顾客的编号. 如果是,控制器就将在customerService上执行一个getCustomer函数,该函数会向服务器发起一次AJAX调用,随后返回的JSON格式的顾客数据将会被填充到$scope属性中,继而会更新HTML模板 .
当用户点击创建按钮时,控制层会调用 createCustormer 函数。 然后,createCustormer 函数会创建一个customer类型的js对象,控制层将js对象传递给服务器,实现将数据保存到数据库中。 示例中使用了微软的WEB API、实体框架,服务器端使用了 SQL Server 数据库,从技术上讲,可以用AngularJS 与任意类型的数据库进行交互。
// customerMaintenanceController.js
"use strict";
define(['application-configuration', 'customersService', 'alertsService'], function (app)
{
app.register.controller('customerMaintenanceController',
['$scope', '$rootScope', '$routeParams', 'customersService', 'alertsService',
function ($scope, $rootScope, $routeParams, customerService, alertsService)
{
$scope.initializeController = function () {
var customerID = ($routeParams.id || "");
$rootScope.alerts = [];
$scope.CustomerID = customerID;
if (customerID == "") {
$scope.CustomerCode = "";
$scope.CompanyName = "";
$scope.Address = "";
$scope.City = "";
$scope.Region = "";
$scope.PostalCode = "";
$scope.CountryCode = "";
$scope.PhoneNumber = ""
$scope.WebSiteURL = "";
$scope.EditMode = true;
$scope.DisplayMode = false;
$scope.ShowCreateButton = true;
$scope.ShowEditButton = false;
$scope.ShowCancelButton = false;
$scope.ShowUpdateButton = false;
}
else
{
var getCustomer = new Object();
getCustomer.CustomerID = customerID;
customerService.getCustomer(getCustomer,
$scope.getCustomerCompleted,
$scope.getCustomerError);
}
}
$scope.getCustomerCompleted = function (response) {
$scope.EditMode = false;
$scope.DisplayMode = true;
$scope.ShowCreateButton = false;
$scope.ShowEditButton = true;
$scope.ShowCancelButton = false;
$scope.ShowUpdateButton = false;
$scope.CustomerCode = response.Customer.CustomerCode;
$scope.CompanyName = response.Customer.CompanyName;
$scope.Address = response.Customer.Address;
$scope.City = response.Customer.City;
$scope.Region = response.Customer.Region;
$scope.PostalCode = response.Customer.PostalCode;
$scope.CountryCode = response.Customer.Country;
$scope.PhoneNumber = response.Customer.PhoneNumber;
$scope.WebSiteURL = response.Customer.WebSiteUrl;
}
$scope.getCustomerError = function (response) {
alertsService.RenderErrorMessage(response.ReturnMessage);
}
$scope.createCustomer = function () {
var customer = $scope.createCustomerObject();
customerService.createCustomer(customer,
$scope.createCustomerCompleted,
$scope.createCustomerError);
}
$scope.createCustomerCompleted = function (response, status) {
$scope.EditMode = false;
$scope.DisplayMode = true;
$scope.ShowCreateButton = false;
$scope.ShowEditButton = true;
$scope.ShowCancelButton = false;
$scope.CustomerID = response.Customer.CustomerID;
alertsService.RenderSuccessMessage(response.ReturnMessage);
$scope.setOriginalValues();
}
$scope.createCustomerError = function (response) {
alertsService.RenderErrorMessage(response.ReturnMessage);
$scope.clearValidationErrors();
alertsService.SetValidationErrors($scope, response.ValidationErrors);
}
$scope.createCustomerObject = function () {
var customer = new Object();
customer.CustomerCode = $scope.CustomerCode;
customer.CompanyName = $scope.CompanyName;
customer.Address = $scope.Address;
customer.City = $scope.City;
customer.Region = $scope.Region;
customer.PostalCode = $scope.PostalCode;
customer.Country = $scope.CountryCode;
customer.PhoneNumber = $scope.PhoneNumber;
customer.WebSiteUrl = $scope.WebSiteURL;
return customer;
}
$scope.clearValidationErrors = function () {
$scope.CustomerCodeInputError = false;
$scope.CompanyNameInputError = false;
}
}]);
});
示例中,显示层和控制层使用 $scope 技术实现 web应用和数据库的双向绑定。在上面的控制层代码中,你可以看到很多地方都使用了 $scope 对象。 在 AngularJS 中,这是实现数据绑定比较常见的方式。 AngularJS 控制层代码近期进行了细微的、影响比较大的优化。
最新的趋势是使用 Controller as ControllerName 这样的语法,而不是直接将$scope注入到你的控制器中。例如,顾客维护控制器可以像如下视图中这样被引用:
<div ng-controller="customerController as customer">
<input ng-model="customer.FirstName" type="text" />
<input ng-model="customer.LastName" type="text" />
<div>
<button ng-click="createCustomer()"/>Create</button>
</div>
填充数据绑定属性的控制器语法就可以像下面这样:
this.FirstName = "";
this.LastName = "";
使用 "this" 对象来引用控制器的scope看上去比直接将$scope注入到控制器中更加清晰。这里需要重申,$scope是“经典”技术,而“controller as"则是AngularJS里更加新晋的东西. 它们俩都能能工作得很好,不管是选择哪一种技术,都要记用着方便为出发点. 现有的实例更多使用的是$scope,而”controller as“则正在慢慢红火起来. 其中一个会比另外一个好么?这我们就得等待并观察AngularJS随时间发生的演变了.