钻研ABP框架的日子,遇到了不少新的知识,由于对本身而言是新知识,因此常常卡在不少地方,迟迟不能有所突破,做为一个稍有上进心的程序员,心里绝对是不服输的,也绝对是不畏困难的,心底必然有这样一股力量“I must conquer it!”。好比,之前没用过AutoMapper,那我就去学,最后将学到的写成了博客,和你们一块儿分享,争取让那些还没接触的人少走弯路。如今,在前端设计时,ABP不少用到的都是AngularJS,而以前作项目都是用的JQuery,因此继续研究ABP也是至关有阻力。可是,不能由于有阻力就拖延,最终致使半途而废吧!于是,仍是得学,毕竟技多不压身嘛!何况,JQuery是MPA【经典】,而AngularJS是SPA【年轻】,这两种框架都掌握了,也算是挺圆满的吧。javascript
如今回到正题,这篇博客,咱们将会看到如何使用AngularJS建立Web应用。对初学者而言,涵盖并解释一些AngularJS框架的基本特征。我设计了一个简单的关于产品的CRUD的例子,解释了演示中全部的代码片断。css
当前,AngularJS做为Javascript的MVC(也有人说是MV*,暂且不纠结这个)框架被普遍使用,它为更快且更容易地开发响应式的Web提供了强大的机制。做为MVC框架,它将Web前端代码分红三个组件Model,View和Controller。所以,在data model,应用逻辑(Controllers)和view展现之间有明确的分离,让你更容易地关注关键的开发区域。view接收来自model的数据来展现。当用户经过点击或者敲击键盘和应用交互时,controller经过改变模型中的数据进行响应。最终,view获得了发生在model中的变化这个通知,从而它能更新展现的内容。html
在Angular应用中,view是DOM(文档对象模型),controller是javascript类,model数据存储在对象属性中。AngularJS将集成在客户端的html和数据传输给浏览器。就像Jquery库同样,为了使用新鲜的model状态更新UI,你必须和DOM交互。例如,不管你什么时候观察到model中的任何改变,你都必须和DOM交互来影响这个改变(若是之前常用Jquery,那么这句话不难理解)。然而,AngularJS提供了数据绑定,咱们没必要将数据从一个地方移到另外一个地方,只须要使用javascript属性映射UI部件,而后它就会自动同步了。前端
如今开始写代码,让咱们一步一步地建立这个简单的CRUD操做案例。java
添加一个index.html页面,并添加一个属性ng-app属性,例如:程序员
<html ng-app="demoApp">
这句代码就将咱们的应用定义为AngularJS应用。由于AngularJS是SPA(单页面应用)框架,因此它会使用html视图模板做为分部视图来响应肯定的路由。为了渲染一个分部视图,咱们会给一个div标签添加一个ng-view属性,全部的分部视图都会渲染在这个div标签中。angularjs
<div ng-view=""></div>
下载两个文件angular.js 和angular-route.js,在index.html中添加这些文件的引用。或者你也能够用CDN的静态资源,好比百度CDN的静态资源http://apps.bdimg.com/libs/angular.js/1.4.6/angular.js:数据库
<script src="../Scripts/angular.min.js"></script> <script src="../Scripts/angular-route.min.js"></script>
如今来点咱们CRUD案例的真实的内容,咱们会开发两个分部视图,ProductList.html和ProductEdit.html。先来看一下ProductList.html的代码:api
<div class="container"> <h2 class="page-title">产品列表</h2> <div class="searchbar"> <ul class="entity-tabular-fields"> <li> <label>搜索:</label> <span class="field-control"> <input type="text" ng-model="filter.productName" value=" " /> </span> <label></label> </li> </ul> </div> <h2><a href="#/ProductEdit">新增产品</a></h2> <table class="items-listing"> <thead> <tr> <th>代码</th> <th>名称</th> <th>描述</th> <th>类别</th> <th>操做</th> </tr> </thead> <tbody> <tr ng-repeat="product in products|filter:filter.productName"> <td><a href="#/ProductEdit?code={{product.code}}">{{product.code}}</a></td> <td>{{product.name}}</td> <td>{{product.description}}</td> <td>{{product.category}}</td> <td><a href="#/?code={{product.code}}">删除</a></td> </tr> </tbody> </table> </div>
效果图:数组
除了html和css,更要关注angular指令的使用。在<tbody>中,咱们使用了指令ng-repeat="product in products | filter:filter.productName"。products是一个javascript数组,它是存储在内存中的。该指令告诉javascript循环遍历数组中的每一个元素(若是你使用过C#的话,那么它就至关于C#中的foreach),而且为每一个元素生成一个<tr>标签。接下来,咱们添加了过滤器,该过滤器根据productName过滤出咱们搜索条件中要求的一些元素,也许正如你看到的,它是一个对象属性,该属性经过数据绑定指令ng-model="filter.productName"绑定在搜索框上。所以不管你在搜索框中输入什么,产品数组都会使用匹配的字符串过滤。为了输出一些值到html上,咱们使用angular的数据绑定语法{{output_Expression}},例如表格单元的产品属性:
<td>{{product.name}}</td>
对于第一个单元格,咱们显示了product.code,它会连接到第二个分部视图ProductEdit.html。
最后一个单元格也包含了一个到列表展现页面的连接,它会使用产品代码product.code做为查询字符串。后面咱们会看到models/controllers的Javascript代码。如今看一下ProductEdit.html代码:
<div class="container"> <h2 class="page-title">产品编辑</h2> <br/> <ul class="entity-tabular-fields"> <li class="entity-field-row"> <label>产品代码:</label> <span class="field-control"> <input type="text" ng-model="currentProduct.code"/> <label></label> </span> </li> <li class="entity-field-row"> <label>产品名称:</label> <span class="field-control"> <input type="text" ng-model="currentProduct.name" /> <label></label> </span> </li> <li class="entity-field-row"> <label>产品描述:</label> <span class="field-control"> <input type="text" ng-model="currentProduct.description"/> </span> <label></label> </li> <li class="entity-field-row"> <label>产品种类:</label> <span class="field-control"> <input type="text" ng-model="currentProduct.category"/> </span> <label></label> </li> <li class="entity-field-row"> <span class="field-control"> <button ng-click="saveProduct()">保存</button> </span> </li> </ul> </div>
效果图:
注意咱们给全部的input标签加了ng-model属性,这个将model(product)属性和相应的UI元素绑定在一块儿。在这个案例中,会自动地建立一个新的叫作“currentProduct”的产品对象,而且使用用户在文本框中输入的值生成该对象的属性。最后,在button标签中添加了一个ng-click的属性,该属性绑定了按钮的点击事件saveProduct,该事件在当前的$scope中。这个$scope,你能够把它认为一个特殊的Javascript对象,咱们当前的视图全部必须的对象或者方法都绑定在$scope上,咱们能够从视图中访问在$scope中声明的任何东西。随着咱们继续推动model或者controller代码,你会更清楚的。
如今,该看看Javascript部分的代码了。以前已经在index.html中添加了两个AngularJS库的引用,如今再添加一个Javascript文件,咱们命名为“App.js”,并将该文件的引用添加到index页面中。
首先,经过调用angular.module声明咱们的应用对象。
var demoApp = angular.module('demoApp', ['ngRoute']);
第一个参数“demoApp”是咱们定义的模块名称,第二参数是咱们的模块必需要有的依赖数组,当前博客的案例中,咱们只依赖“ngRoute”。
如今咱们为分部视图配置路由:
//配置路由
demoApp.config(function($routeProvider) { $routeProvider .when('/', { controller: "ProductController", templateUrl: "partials/ProductList.html" }) .when('/ProductEdit', { controller: "ProductController", templateUrl: "partials/ProductEdit.html" }) .otherwise({ redirectTo: '/' }); });
参数$routeProvider传到路由配置的函数中,而后使用了一个链式的when函数来寻找url路径,而且传递一个对象来定义和controller相联系的目标视图。Controller属性定义了在templateUrl属性中指定的分部视图会使用哪个控制器。咱们的例子中只使用了一个控制器ProductController。otherwise()函数的做用是,若是没有指定的路径匹配到请求的url路径,就会匹配的默认视图。能够联想一下switch…case…default语法。
是时候定义ProductController了,在AngularJS中,有多种方式定义控制器。我么如今使用一个controllers对象,而后把全部的controller加到这个对象中,最后将这个对象配置给咱们app模块的controllers属性。
每个controller都必须传递一个$scope参数,视图要使用它来访问data model中的数据。在$scope中定义的任何数据均可以在视图中直接访问。若是须要的话也可使用一些其余可选的参数$route
, $routeParams
, $location
。
在这个控制器中,咱们添加了和ProductFactory交互的支持函数,经过添加到$scope对象中使得这些函数在视图中可使用。咱们用到了一个事件“$viewContentLoaded”,这个事件在分部视图加载到div标签中时触发。由于咱们对于添加和编辑任务都是用的ProductEdit页面,所以我将被编辑的产品的code做为查询字符串参数。而后在这个事件中检测查询参数code,若是有此参数,视图就是编辑模式,不然就是一个建立新产品的页面。删除也是相似的原理,若是咱们在查询字符串参数中检测到了code,那么就删除产品,而后刷新列表。
你可能会注意到控制器中的另外一个参数ProductFactory,这是咱们须要从控制器访问数据的服务组件。Angular框架容许用不一样的方式建立服务组件。最经常使用的一种方式是使用工厂建立。咱们须要在应用模块中添加该服务,该应用模块包含两个参数:服务名称和工厂函数。别惧怕,它不过是一个返回新对象的简单函数。
1 //定义controllers对象 2 var controllers = {}; 3 4 controllers.ProductController= function($scope,$route,$routeParams,$location,ProductFactory) { 5 $scope.products = []; 6 7 var init= function() { 8 $scope.products = ProductFactory.getProducts(); 9 } 10 11 var initProductEdit= function() { 12 var code = $routeParams.code; 13 if (code==undefined) { 14 $scope.currentProduct = {}; 15 } else { 16 $scope.currentProduct = ProductFactory.loadProductByCode(code); 17 } 18 } 19 20 $scope.$on('$viewContentLoaded', function() { 21 var tempalteUrl = $route.current.templateUrl; 22 if (tempalteUrl=="partials/ProductEdit.html") { 23 initProductEdit(); 24 }else if (tempalteUrl=="partials/ProductList.html") {//大小写要和temlateUrl中的大小写保持一致 25 var code = $routeParams.code; 26 if (code!=undefined) { 27 $scope.deleteProduct(code); 28 } 29 } 30 }); 31 32 init(); 33 34 $scope.saveProduct= function() { 35 ProductFactory.saveProduct($scope.currentProduct); 36 $location.search('code', null); 37 $location.path('/'); 38 } 39 40 $scope.deleteProduct= function(code) { 41 ProductFactory.deleteProduct(code); 42 $location.search('code', null); 43 $location.path('/'); 44 } 45 } 46 47 //将全部的控制器赋值给app模块 48 demoApp.controller(controllers); 49 50 //定义工厂 51 demoApp.factory('ProductFactory', function () { 52 //初始化产品数组 53 var products = [ 54 {code:'P001',name:'Lumia 950XL',description:'win10系统最好的手机,带有黑科技色彩',category:'mobile'}, 55 {code:'P002',name:'Lumia 950',description:'win10系统次好的手机,相比XL低个档次',category:'mobile'}, 56 {code:'P003',name:'Surface Pro Book',description:'微软最具创新的笔记本',category:'Notebook'}, 57 {code:'P004',name:'Surface Pro 4',description:'微软最好的PC/平板二合一产品',category:'Surface'}, 58 { code: 'P005', name: 'Surface 4', description: '微软次好的PC/平板二合一产品', category: 'Surface' }, 59 {code:'P006',name:'Surface Phone',description:'传说中微软下一代win10系统超旗舰手机',category:'mobile'} 60 ]; 61 62 var factory = {}; 63 factory.getProducts= function() { 64 return products; 65 } 66 67 factory.loadProductByCode= function(code) { 68 var productFound={}; 69 for (var i = 0; i < products.length; i++) { 70 if (products[i].code==code) { 71 productFound = products[i]; 72 break; 73 } 74 } 75 return productFound; 76 } 77 78 factory.saveProduct= function(product) { 79 var tempProduct = factory.loadProductByCode(product.code); 80 81 if (tempProduct == null || tempProduct == undefined) { 82 tempProduct = {}; 83 tempProduct.code = product.code; 84 tempProduct.name = product.name; 85 tempProduct.description = product.description; 86 tempProduct.category = product.category; 87 } else{ 88 89 tempProduct.code = product.code; 90 tempProduct.name = product.name; 91 tempProduct.description = product.description; 92 tempProduct.category = product.category; 93 94 products.push(tempProduct); 95 } 96 } 97 98 factory.deleteProduct= function(code) { 99 var tempProduct = factory.loadProductByCode(code); 100 101 if (tempProduct!=null) { 102 products.remove(tempProduct); 103 } 104 } 105 return factory; 106 });
如上面的代码,咱们使用模块的工厂函数来定义工厂服务组件。代码很明显,它只包含了一些帮助属性和方法。代码末尾的deleteProduct函数使用了一个数组的remove函数,它不是默认的数组函数,我定义了一个函数,把它添加到Array.prototype中,使得它能够在网站中的任何地方均可以访问。但要确保在应用加载的开始处理这个,在一些相关的代码执行以前执行,好比我放到了index.html页面中的script标签中。
在工厂组件中,咱们常常必须从server中存取数据,但在这篇博客中,咱们只用到了临时的内存中的产品数组。在下一篇博客中,我将从server的数据库中存取数据。
注解:
$route:用于控制器和视图(html分部视图)的深度连接。查看更多。
$routeParams:该服务容许检索当前路由参数的集合。查看更多。
$location:该服务分析浏览器地址栏中的URL(基于window.location),使得该URL对应用可用。查看更多。