Angular(一)
一、Angular优点:
1、MVC:职责清晰,代码模块化。
2、模块化
3、指定系统——directive:我们已经在模板中看到了一些新的属性,这些属性不属于HTML规范。例如:我们引入了双花括号用来实现数据绑定;引入了ng-controller用来指定每个控制器负责监视视图中的哪一部分;引入ng-model,用来把输入数据绑定到模型中的一部分属性上。我们把这些指令叫做HTML扩展指令。
4、双向数据绑定
5、依赖注入:进行数据绑定的$scope对象会被自动传递给我们;我们并不需要调用任何函数去创建这个对象。只要把$scope对象放在控制器的构造函数里面,然后就可以获取它了。并且$scope并不是我们唯一可以获取的东西。如果我们想把数据绑定到用户浏览器中的URL地址上,可以使用$location对象,只要把$location对象放到我们的构造函数中即可,例如:
function HelloCtroller($scope,$location) { $scope.greeting={text:'Hello'};
//这里可以使用$location对象来做一些很酷的事儿 }
这种神奇的效果是通过angular的依赖注入机制实现的。依赖注入让我们遵守这样一种开发风格:我们的类只是简单获取它们所需要的东西,而不需要创建那些它们所依赖的东西。
二、Model View Controller
<!DOCTYPE html> <html lang="en" ng-app='myApp'> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body ng-controller='TextController'> <p>{{someText.message}}</p> <script src='angular-1.3.0.js'></script> <script> var myApp=angular.module('myApp',[]); myApp.controller("TextController",["$scope",function($scope){ var someText={}; someText.message='hahhaha'; $scope.someText=someText; }]); </script> </body> </html>
其中,<p>{{someText.message}}</p> 采用双花括号插值语法,把新的内容插入到现有的模板中。
控制器就是你所编写的类或者类型,它的作用是告诉Angular该模型是由哪些对象或者基本数据构成的,只要把这些队形或者基本数据设置到$scope对象上即可,$scope对象会被传递给控制器。
通过$scope对象把数据模型和函数暴露给视图(UI模板)。
这个版本中,我们把ng-app属性设置成了模块的名称myApp。然后我们再调用angular对象创建了一个名为myApp的模块,并且把控制器函数传递给了myApp模块的controller函数。
采用模块机制可以把东西从全局命名空间中隔离开。
三、模块和数据绑定
1、表单输入
<!DOCTYPE html> <html lang="en" ng-app='myApp'> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <form ng-controller = "SomeController"> Starting:<input ng-change='computeNeeded()' ng-model="funding.startingEstimate"> Recommendation:{{funding.needed}} </form> <script src='angular-1.3.0.js'></script> <script> var myApp=angular.module('myApp',[]); myApp.controller("SomeController",["$scope",function($scope){ $scope.funding={startingEstimate:0}; $scope.computeNeeded=function(){ $scope.funding.needed=$scope.funding.startingEstimate*10; } }]); </script> </body> </html>
对于输入元素说,可以使用ng-change属性来指定一个控制器方法,一旦用户修改了输入元素,这个方法就会被调用。
当用户在这个特定的输入框中输入时,输入框就会正确地刷新。但是,如果还有其他输入框也绑定到模型中的这个属性上,会怎么样呢?如果接受到服务器的数据,导致数据模型进行刷新,又会怎样?
为了能够正确地刷新输入框,而不管他是通过何种途径进行刷新的,我们需要使用$scope中的$watch函数。最基本的一点就是,可以调用$watch()函数来监视一个表达式,当这个表达式发生变化时就会调用一个回调函数。
<!DOCTYPE html> <html lang="en" ng-app='myApp'> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <form ng-controller = "SomeController"> Starting:<input ng-model="funding.startingEstimate"> Recommendation:{{funding.needed}} </form> <script src='angular-1.3.0.js'></script> <script> var myApp=angular.module('myApp',[]); myApp.controller("SomeController",["$scope",function($scope){ $scope.funding={startingEstimate:0}; computeNeeded=function(){ $scope.funding.needed=$scope.funding.startingEstimate*10; console.log($scope.funding.startingEstimate); }; $scope.$watch('funding.startingEstimate',computeNeeded) }]); </script> </body> </html>
请注意,$scope.$watch('funding.startingEstimate',computeNeeded) 需要监视的表达式位于引号中,它是一个字符串。这个字符串会被当做angular表达式来执行。当他的值发生变化时,就调用computeNeeded()。
2、列表、表格以及其他迭代型元素
<!DOCTYPE html> <html lang="en" ng-app='myApp'> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body ng-controller='TextController'> <ul> <li ng-repeat='student in students'> <a href="/student/view/{{student.id}}">{{student.name}}</a> </li> </ul> <button ng-click='insertTom()'>Insert</button> <script src='angular-1.3.0.js'></script> <script> var myApp=angular.module('myApp',[]); var students=[{name:'Mary',id:'1'},{name:'Jack',id:'2'},{name:'Jill',id:'3'}]; myApp.controller("TextController",["$scope",function($scope){ $scope.students=students; $scope.insertTom=function(){ $scope.students.splice(1,0,{name:'Tom',id:'4'}) } }]); </script> </body> </html>
ng-repeat指令可以通过$index返回当前引用的元素序号;还可以通过$first、$middle、$last,ng-repeat指令返回布尔值,告诉你当前元素是否是集合中的第一个元素、中间的某个元素,或者最后一个元素。
3、隐藏和显示/CSS类和样式
<!DOCTYPE html> <html lang="en" ng-app='myApp'> <head> <meta charset="UTF-8"> <title>Document</title> <style> .menu-disabled-true{background:gray;} </style> </head> <body ng-controller='TextController'> <button ng-click='toggleMenu()'>Toggle Menu</button> <ul ng-show='menuState'> <li class='menu-disabled-{{isDisabled}}' ng-click="stun()">stun</li> <li ng-click="disintegrate()">disintegrate</li> <li ng-click="erase()">erase from history</li> </ul> <script src='angular-1.3.0.js'></script> <script> var myApp=angular.module('myApp',[]); myApp.controller("TextController",["$scope",function($scope){ $scope.menuState=true; $scope.isDisabled=false; $scope.toggleMenu=function(){ $scope.menuState= !$scope.menuState; } $scope.stun=function(){ $scope.isDisabled=true; } }]); </script> </body> </html>
现在可以动态的设置css类和样式了,只要使用{{}}插值语法把它们进行数据绑定即可。
4、ng-class、ng-style
<!DOCTYPE html> <html lang="en" ng-app='myApp'> <head> <meta charset="UTF-8"> <title>Document</title> <style> .error{ background: red; } .warning{ background: yellow; } </style> </head> <body ng-controller='TextController'> <div ng-class="{error:isError,warning:isWarning}"> {{messageTxt}} </div> <button ng-click="showError()">Simulate Error</button> <button ng-click="showWarning()">Simulate Warning</button> <script src='angular-1.3.0.js'></script> <script> var myApp=angular.module('myApp',[]); myApp.controller("TextController",["$scope",function($scope){ $scope.isError=false; $scope.isWarning=false; $scope.showError=function(){ $scope.messageTxt='This is an error'; $scope.isError=true; $scope.isWarning=false; }; $scope.showWarning=function(){ $scope.messageTxt='Just a warning.Please carry on.'; $scope.isWarning=true; $scope.isError=false; } }]); </script> </body> </html>
若需要将表格中被选中的行进行高亮显示:
<!DOCTYPE html> <html lang="en" ng-app='myApp'> <head> <meta charset="UTF-8"> <title>Document</title> <style> .selected{background: lightgreen;} </style> </head> <body ng-controller='TextController'> <table> <tr ng-repeat='student in students' ng-click='select($index)' ng-class='{selected:$index==selectedRow}'> <td>{{student.id}}</td> <td>{{student.name}}</td> </tr> </table> <script src='angular-1.3.0.js'></script> <script> var myApp=angular.module('myApp',[]); var students=[{name:'Mary',id:'1'},{name:'Jack',id:'2'},{name:'Jill',id:'3'}]; myApp.controller("TextController",["$scope",function($scope){ $scope.students=students;
$scope.select=function(row){ $scope.selectedRow=row; } }]); </script> </body> </html>
在上面中,我们把ng-class设置为{selected:$index==selectRow}。这样做的效果就是,当模型属性selectRow的值等于ng-repeat中的$index时,selected样式就会被设置到那一行上。而ng-click则是告诉控制器用户点击了哪一行。
5、使用$watch 监控数据模型的变化
当你的数据模型中某一部分发生变化时,$watch函数可以向你发出通知。你可以监控单个对象的属性,也可以监控需要经过计算的结果(函数),实际上只要能够被当作属性访问到,或者可以当做一个Javascript函数被计算出来,就可以被$watch函数监控。它的函数签名为:$watch(watchFn,watchAction,deepWatch);
$watch函数会返回一个函数,当你不再需要接收变更通知时,可以用这个返回的函数注销监控器。
将(二)中的购物车功能进行扩充完整。例如,当用户添加到购物车中的商品价值超过100美元时,我们会给他10美元的折扣。如下:
1 <!DOCTYPE html> 2 <html lang="en" ng-app='myApp'> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Document</title> 6 </head> 7 <body ng-controller='CartController'> 8 <h1>Your Order</h1> 9 <div ng-repeat='item in items'> 10 <span>{{item.title}}</span> 11 <input type="text" ng-model='item.quality'> 12 <span>{{item.price|currency}}</span> 13 <span>{{item.price*item.quality|currency}}</span> 14 <button ng-click='remove($index)'>Remove</button> 15 </div> 16 <div>total:{{totalCart()|currency}}</div> 17 <div>discount:{{bill.discount|currency}}</div> 18 <div>subtotal:{{subtotal()|currency}}</div> 19 <script src='angular-1.3.0.js'></script> 20 <script> 21 var myApp=angular.module('myApp',[]); 22 myApp.controller("CartController",["$scope",function($scope){ 23 $scope.bill={}; 24 $scope.items=[ 25 {title:'Paint pots',quality:8,price:3.95}, 26 {title:'Polka dots',quality:17,price:12.95}, 27 {title:'Pebbles',quality:5,price:6.95} 28 ]; 29 $scope.totalCart=function(){ 30 var total=0; 31 for (var i = 0,len = $scope.items.length; i<len;i++) { 32 total=total+$scope.items[i].price*$scope.items[i].quality; 33 } 34 return total; 35 } 36 $scope.subtotal=function(){ 37 return $scope.totalCart()-$scope.bill.discount; 38 } 39 function calculateDiscount(newValue,oldValue,scope){ 40 $scope.bill.discount=newValue>100?10:0; 41 } 42 $scope.$watch($scope.totalCart,calculateDiscount); 43 $scope.remove=function(index){ 44 console.log(index); 45 $scope.items.splice(index,1) 46 } 47 48 }]); 49 </script> 50 </body> 51 </html>
另:watch中的性能注意事项:
上个例子可以正确地运行,但是却潜在性能问题。如果你在totalCart()中打个调试断点,你会发现在渲染这个页面时,该函数被调用了6次。虽然在当前这个应用中它所引起的性能问题并不明显,但是在更加复杂的应用中,运行6次就会成为一个问题。
为什么是6次呢?其中3次我们可以很容易跟踪到,因为在以下每种情况下,它都会运行一次:
- 模板{{totalCart()|currency}}
- subtotal()函数
- $watch()函数
然后angular把以上整个过程又重复了一遍,最终是6次。angular这样做的目的是,检测模型中的变更已经被完整地进行了传播,并且模型已经被设置好。angular的做法是,把所有被监控的属性都拷贝一份,然后把它们和当前的值进行比较,看看是否发生了变化。
有几种方法可以解决这个问题。一种方法是监控items数组的变化,然后重新计算$scope属性中的总价、折扣和小计值。
<!DOCTYPE html> <html lang="en" ng-app='myApp'> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body ng-controller='CartController'> <h1>Your Order</h1> <div ng-repeat='item in items'> <span>{{item.title}}</span> <input type="text" ng-model='item.quality'> <span>{{item.price|currency}}</span> <span>{{item.price*item.quality|currency}}</span> <button ng-click='remove($index)'>Remove</button> </div> <div>total:{{bill.totalCart|currency}}</div> <div>discount:{{bill.discount|currency}}</div> <div>subtotal:{{bill.subtotal|currency}}</div> <script src='angular-1.3.0.js'></script> <script> var myApp=angular.module('myApp',[]); myApp.controller("CartController",["$scope",function($scope){ $scope.bill={}; $scope.items=[ {title:'Paint pots',quality:8,price:3.95}, {title:'Polka dots',quality:17,price:12.95}, {title:'Pebbles',quality:5,price:6.95} ]; $scope.calculateTotals=function(){ var total=0; for (var i = 0,len = $scope.items.length; i<len;i++) { total=total+$scope.items[i].price*$scope.items[i].quality; } $scope.bill.totalCart=total; $scope.bill.discount=total>100?10:0; $scope.bill.subtotal=total-$scope.bill.discount; }; $scope.$watch("items",$scope.calculateTotals,true);//此处第三个参数传递了一个true,请求angular遍历items的属性,
当其中任何一个属性变化时就调用$scope函数 $scope.remove=function(index){ console.log(index); $scope.items.splice(index,1) } }]); </script> </body> </html>
请注意,上面的代码在调用$watch函数时把items写成了一个字符串。这样做是可以的,因为$watch函数既可以接受一个函数(就像我们之前说的),也可以接受一个字符串。如果把一个字符串传递给了$watch函数,它将会在被调用的$watch作用域中当成表达式来执行。
对于这种应用来说,这种策略可能会工作得很好。但是,既然我们在监控items数组,angular就会制作一份数组的拷贝,用来进行比较操作。对于大型的items数组来说,如果每次在angular显示页面时只需要重新计算bill属性,那么性能会好很多。
可以通过创建一个带有watchFn的$watch函数我们就可以实现这一点,watchFn将会像下面这样来重新计算属性值:
myApp.controller("CartController",["$scope",function($scope){ $scope.bill={}; $scope.items=[ {title:'Paint pots',quality:8,price:3.95}, {title:'Polka dots',quality:17,price:12.95}, {title:'Pebbles',quality:5,price:6.95} ]; $scope.$watch(function(){ var total=0; for (var i = 0,len = $scope.items.length; i<len;i++) { total=total+$scope.items[i].price*$scope.items[i].quality; } $scope.bill.totalCart=total; $scope.bill.discount=total>100?10:0; $scope.bill.subtotal=total-$scope.bill.discount; }) $scope.remove=function(index){ console.log(index); $scope.items.splice(index,1) } }]);