前言


钻研ABP框架的日子,遇到了很多新的知识,因为对自己而言是新知识,所以经常卡在很多地方,迟迟不能有所突破,作为一个稍有上进心的程序员,内心绝对是不服输的,也绝对是不畏困难的,心底必然有这样一股力量“I must conquer it!”。比如,以前没用过AutoMapper,那我就去学,最后将学到的写成了博客,和大家一起分享,争取让那些还没接触的人少走弯路。现在,在前端设计时,ABP很多用到的都是AngularJS,而之前做项目都是用的JQuery,所以继续研究ABP也是相当有阻力。但是,不能因为有阻力就拖延,最终导致半途而废吧!因而,还是得学,毕竟技多不压身嘛!况且,JQuery是MPA【经典】,而AngularJS是SPA【年轻】,这两种框架都掌握了,也算是挺圆满的吧。

现在回到正题,这篇博客,我们将会看到如何使用AngularJS创建Web应用。对初学者而言,涵盖并解释一些AngularJS框架的基本特征。我设计了一个简单的关于产品的CRUD的例子,解释了演示中所有的代码片段。

背景


当前,AngularJS作为Javascript的MVC(也有人说是MV*,暂且不纠结这个)框架被广泛使用,它为更快且更容易地开发响应式的Web提供了强大的机制。作为MVC框架,它将Web前端代码分成三个组件Model,View和Controller。因此,在data model,应用逻辑(Controllers)和view展示之间有明确的分离,让你更容易地关注关键的开发区域。view接收来自model的数据来展示。当用户通过点击或者敲击键盘和应用交互时,controller通过改变模型中的数据进行响应。最终,view得到了发生在model中的变化这个通知,从而它能更新展示的内容。

在Angular应用中,view是DOM(文档对象模型),controller是javascript类,model数据存储在对象属性中。AngularJS将集成在客户端的html和数据传输给浏览器。就像Jquery库一样,为了使用新鲜的model状态更新UI,你必须和DOM交互。例如,无论你何时观察到model中的任何改变,你都必须和DOM交互来影响这个改变(如果以前经常使用Jquery,那么这句话不难理解)。然而,AngularJS提供了数据绑定,我们不必将数据从一个地方移到另一个地方,只需要使用javascript属性映射UI部件,然后它就会自动同步了。

使用代码


源代码下载

现在开始写代码,让我们一步一步地创建这个简单的CRUD操作案例。

添加一个index.html页面,并添加一个属性ng-app属性,例如:

<html ng-app="demoApp">

这句代码就将我们的应用定义为AngularJS应用。因为AngularJS是SPA(单页面应用)框架,所以它会使用html视图模板作为分部视图来响应确定的路由。为了渲染一个分部视图,我们会给一个div标签添加一个ng-view属性,所有的分部视图都会渲染在这个div标签中。

<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的代码:

<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/Angular 部分


现在,该看看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框架允许用不同的方式创建服务组件。最常用的一种方式是使用工厂创建。我们需要在应用模块中添加该服务,该应用模块包含两个参数:服务名称和工厂函数。别害怕,它不过是一个返回新对象的简单函数。

 //定义controllers对象
var controllers = {}; controllers.ProductController= function($scope,$route,$routeParams,$location,ProductFactory) {
$scope.products = []; var init= function() {
$scope.products = ProductFactory.getProducts();
} var initProductEdit= function() {
var code = $routeParams.code;
if (code==undefined) {
$scope.currentProduct = {};
} else {
$scope.currentProduct = ProductFactory.loadProductByCode(code);
}
} $scope.$on('$viewContentLoaded', function() {
var tempalteUrl = $route.current.templateUrl;
if (tempalteUrl=="partials/ProductEdit.html") {
initProductEdit();
}else if (tempalteUrl=="partials/ProductList.html") {//大小写要和temlateUrl中的大小写保持一致
var code = $routeParams.code;
if (code!=undefined) {
$scope.deleteProduct(code);
}
}
}); init(); $scope.saveProduct= function() {
ProductFactory.saveProduct($scope.currentProduct);
$location.search('code', null);
$location.path('/');
} $scope.deleteProduct= function(code) {
ProductFactory.deleteProduct(code);
$location.search('code', null);
$location.path('/');
}
} //将所有的控制器赋值给app模块
demoApp.controller(controllers); //定义工厂
demoApp.factory('ProductFactory', function () {
//初始化产品数组
var products = [
{code:'P001',name:'Lumia 950XL',description:'win10系统最好的手机,带有黑科技色彩',category:'mobile'},
{code:'P002',name:'Lumia 950',description:'win10系统次好的手机,相比XL低个档次',category:'mobile'},
{code:'P003',name:'Surface Pro Book',description:'微软最具创新的笔记本',category:'Notebook'},
{code:'P004',name:'Surface Pro 4',description:'微软最好的PC/平板二合一产品',category:'Surface'},
{ code: 'P005', name: 'Surface 4', description: '微软次好的PC/平板二合一产品', category: 'Surface' },
{code:'P006',name:'Surface Phone',description:'传说中微软下一代win10系统超旗舰手机',category:'mobile'}
]; var factory = {};
factory.getProducts= function() {
return products;
} factory.loadProductByCode= function(code) {
var productFound={};
for (var i = 0; i < products.length; i++) {
if (products[i].code==code) {
productFound = products[i];
break;
}
}
return productFound;
} factory.saveProduct= function(product) {
var tempProduct = factory.loadProductByCode(product.code); if (tempProduct == null || tempProduct == undefined) {
tempProduct = {};
tempProduct.code = product.code;
tempProduct.name = product.name;
tempProduct.description = product.description;
tempProduct.category = product.category;
} else{ tempProduct.code = product.code;
tempProduct.name = product.name;
tempProduct.description = product.description;
tempProduct.category = product.category; products.push(tempProduct);
}
} factory.deleteProduct= function(code) {
var tempProduct = factory.loadProductByCode(code); if (tempProduct!=null) {
products.remove(tempProduct);
}
}
return factory;
});

点击查看代码

如上面的代码,我们使用模块的工厂函数来定义工厂服务组件。代码很明显,它只包含了一些帮助属性和方法。代码末尾的deleteProduct函数使用了一个数组的remove函数,它不是默认的数组函数,我定义了一个函数,把它添加到Array.prototype中,使得它可以在网站中的任何地方都可以访问。但要确保在应用加载的开始处理这个,在一些相关的代码执行之前执行,比如我放到了index.html页面中的script标签中。

在工厂组件中,我们经常必须从server中存取数据,但在这篇博客中,我们只用到了临时的内存中的产品数组。在下一篇博客中,我将从server的数据库中存取数据。

注解:

$route:用于控制器和视图(html分部视图)的深度链接。查看更多。

$routeParams:该服务允许检索当前路由参数的集合。查看更多。

$location:该服务分析浏览器地址栏中的URL(基于window.location),使得该URL对应用可用。查看更多。

源代码下载

AngularJS实战项目(Ⅰ)--含源码的更多相关文章

  1. Visual Studio 2015开发Qt项目实战经验分享(附项目示例源码)

    Visual Studio 2015开发Qt项目实战经验分享(附项目示例源码)    转 https://blog.csdn.net/lhl1124281072/article/details/800 ...

  2. 百度智能手环方案开源(含源码,原理图,APP,通信协议等)

    分享一个百度智能手环开源项目的设计方案资料. 项目简介 百度云智能手环的开源方案是基于Apache2.0开源协议,开源内容包括硬件设计文档,原理图.ROM.通讯协议在内的全套方案,同时开放APP和云服 ...

  3. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  4. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  5. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  6. Maven自定义绑定插件目标:创建项目的源码jar

    <build> <plugins> <!-- 自定义绑定,创建项目的源码jar --> <plugin> <groupId>org.apac ...

  7. Eclipse 关联项目的源码

    Eclipse 关联项目的源码 1.jdk源码的关联: 一般jdk自带的类,显示其源码的方式: 用jdk自带的src.zip吧:我的JDK目录是:D:\Java\jdk1.6.0_10 , JRE目录 ...

  8. C++ JsonCpp 使用(含源码下载)

    C++ JsonCpp 使用(含源码下载) 前言 JSON是一个轻量级的数据定义格式,比起XML易学易用,而扩展功能不比XML差多少,用之进行数据交换是一个很好的选择JSON的全称为:JavaScri ...

  9. 微信公众平台开发-OAuth2.0网页授权(含源码)

    微信公众平台开发-OAuth2.0网页授权接口.网页授权接口详解(含源码)作者: 孟祥磊-<微信公众平台开发实例教程> 在微信开发的高级应用中,几乎都会使用到该接口,因为通过该接口,可以获 ...

  10. 微信公众平台开发-access_token获取及应用(含源码)

    微信公众平台开发-access_token获取及应用(含源码)作者: 孟祥磊-<微信公众平台开发实例教程> 很多系统中都有access_token参数,对于微信公众平台的access_to ...

随机推荐

  1. c++的类与对象

    对象:此对象,非彼对象,:-D,跟妹子无关(不过貌似也可以,,),闲言少叙,书归正传 我们可以把我们见到的一切事物都称为对象.它可以有形,可以无形,可以简单,可以复杂,但是对某一种具体的对象,比如公司 ...

  2. sql按字段值进行统计

    用group by 如有个student表里有性别sex来统计 select sex,count(*) from student group by sex;

  3. cocos2d-js版本A*算法

    [转]http://blog.csdn.net/realcrazysun1/article/details/43054229 A*算法的东西网上讲了很多~但还是不可避免的要去研究一下,cocos官网上 ...

  4. VC++ MFC中如何将应用程序的配置信息保存到注册表中(二)

    在上一篇中介绍了几个写入注册表数据和读取注册表数据的接口,并介绍了使用方法. 这一片教你如何使得你的应用程序在下次打开时保持上一次关闭前的状态. 在上一篇添加的代码的基础上,要添加WM_CLOSE消息 ...

  5. sql Server中SET QUOTED_IDENTIFIER的使用

    在存储过程中经常会有 Sql代码   SET QUOTED_IDENTIFIER on SET QUOTED_IDENTIFIER off 这样的语句,那么SET QUOTED_IDENTIFIER到 ...

  6. PHP调试总结

    PHP调试总结一,环境方面,比如查看安装扩展是否生效,是总支持某扩展.可以在web目录中建一个phpinfo.php在里面输入<?phpphpinfo();?>在浏览器上访问一下,会输出P ...

  7. eclipse 配置黑色主题

    虽然以前也使用eclipse的黑色主题,但是配置起来稍微麻烦一点. 这里先声明,下面的方式适合最新版本的Eclipse Luna,旧的版本可以下载我提供的这个插件,并将其放在eclipse目录下的pl ...

  8. Ubuntu遇到Please ensure that adb is correctly located at '...adb.exe' and can be executed 问题解决方法

    上次我们在SDK更新的到最新的Android L版本之后,我发现我的ADT和android指定的版本不对应,我的ADT是22版本的,android L需要23版本以上的,版本不对应的话就无法加载这个S ...

  9. 第四章 Leader选举算法分析

    Leader选举 学习leader选举算法,主要是从选举概述,算法分析与源码分析(后续章节写)三个方面进行. Leader选举概述 服务器启动时期的Leader选举 选举的隐式条件便是ZooKeepe ...

  10. Linux聊天室项目 -- ChatRome(select实现)

    序 项目简介:采用I/O复用技术select实现socket通信,采用多线程负责每个客户操作处理,完成Linux下的多客户聊天室! OS:Ubuntu 15.04 IDE:vim gcc make D ...