AngularJS学习笔记之TodoMVC的分析
最近一段时间一直在看AngularJS,趁着一点时间总结一下。
官网地址:http://angularjs.org/
先推荐几个教程
1.AngularJS入门教程比较基础,是官方Tutorial的翻译。
2.七步从AngularJS菜鸟到专家也比较基础,制作了一个在线音乐播放网站。
3.AngularJS开发指南这个教程比较全面,但我感觉翻译的有些晦涩难懂。
看过这些教程后,觉得AngularJS也懂一点了,就想用它干点事,就分析一下AngularJS写的todomvc吧。
Todomvc官网地址:http://todomvc.com/
项目的目录如下:
bower_components里放了两个文件夹,其中angular文件夹是用来一如angular.js文件的,todomvc-common文件夹里的放入了所有todo项目统一的css\js(只是用来生成左侧内容的,与项目无关)和图片。
js文件夹是大头,里面放了相应的controller(控制器)\directive(指令)\service(服务)和app.js。
test文件夹里放的是测试用的代码,不分析。
index.html是项目的view页面。
先来看一下app.js
/*globalangular*/ /*jshintunused:false*/ 'usestrict'; /** *ThemainTodoMVCappmodule * *@type{angular.Module} */ vartodomvc=angular.module('todomvc',[]);
就是定义了一个模块todomvc
再看一下services下的todoStorage.js
/*globaltodomvc*/ 'usestrict'; /** *ServicesthatpersistsandretrievesTODOsfromlocalStorage */ todomvc.factory('todoStorage',function(){ //todosJSON字符串存储的唯一标识 varSTORAGE_ID='todos-angularjs'; return{ //从localStorage中取出todos,并解析成JSON对象 get:function(){ returnJSON.parse(localStorage.getItem(STORAGE_ID)||'[]'); }, //将todos对象转化成JSON字符串,并存入localStorage put:function(todos){ localStorage.setItem(STORAGE_ID,JSON.stringify(todos)); } }; });
使用factory方法创建了todoStorage的service方法,这个service方法的本质就是返回了两个方法get和put,两者都是用了JSON2和HTML5的特性。get将todos的内容从localStorage中取出,并解析成JSON,put将todos转化成JSON字符串,并存储到localStorage中。
再看一下directives下面的两个指令文件。
todoFocus.js
/*globaltodomvc*/ 'usestrict'; /** *Directivethatplacesfocusontheelementitisappliedtowhentheexpressionitbindstoevaluatestotrue */ todomvc.directive('todoFocus',functiontodoFocus($timeout){ returnfunction(scope,elem,attrs){ //为todoFocus属性的值添加监听 scope.$watch(attrs.todoFocus,function(newVal){ if(newVal){ $timeout(function(){ elem[0].focus(); },0,false); } }); }; });
返回function的参数中,elem就是包含该指令的元素的数组,attrs是元素的所有属性、属性名等组成的对象。
其中用到了两个AngularJS的方法
$watch(watchExpression,listener,objectEquality)注册一个侦听器回调,每当watchExpression变化时,监听回调将被执行。
$timeout(fn[,delay][,invokeApply])当timeout的值达到时,执行fn函数。
todoFocus.js创建了todoFocus指令。当一个元素拥有todoFocus属性时,该指令会为该元素的todoFocus属性的值添加监听,如果todoFocus属性的值改变成true,就会执行$timeout(function(){elem[0].focus();},0,false);其中的延迟时间为0秒,所以会立即执行elem[0].focus()。
todoEscape.js
/*globaltodomvc*/ 'usestrict'; /** *Directivethatexecutesanexpressionwhentheelementitisappliedtogets *an`escape`keydownevent. */ todomvc.directive('todoEscape',function(){ varESCAPE_KEY=27; returnfunction(scope,elem,attrs){ elem.bind('keydown',function(event){ if(event.keyCode===ESCAPE_KEY){ scope.$apply(attrs.todoEscape); } }); }; });
todoEscape.js创建了todoEscape指令。当按下Escape键时,执行attrs.todoEscape的表达式。
看一下大头,controllers文件夹中的todoCtrl.js,这个文件略长,我就直接写注释了。
/*globaltodomvc,angular*/ 'usestrict'; /** *Themaincontrollerfortheapp.Thecontroller: *-retrievesandpersiststhemodelviathetodoStorageservice *-exposesthemodeltothetemplateandprovideseventhandlers */ todomvc.controller('TodoCtrl',functionTodoCtrl($scope,$location,todoStorage,filterFilter){ //从localStorage中获取todos vartodos=$scope.todos=todoStorage.get();
//记录新的todo $scope.newTodo=''; //记录编辑过的todo $scope.editedTodo=null; //当todos的值改变时执行其中的方法 $scope.$watch('todos',function(newValue,oldValue){ //获取未完成的todos的数目 $scope.remainingCount=filterFilter(todos,{completed:false}).length; //获取已完成的todos的数目 $scope.completedCount=todos.length-$scope.remainingCount; //当且仅当$scope.remainingCount为0时,$scope.allChecked为true $scope.allChecked=!$scope.remainingCount; //当todos的新值和旧值不相等时,向localStorage中存入todos if(newValue!==oldValue){//Thispreventsunneededcallstothelocalstorage todoStorage.put(todos); } },true); if($location.path()===''){ //如果$location.path()为空,就设置为/ $location.path('/'); } $scope.location=$location; //当location.path()的值改变时执行其中的方法 $scope.$watch('location.path()',function(path){ //获取状态的过滤器 //如果path为'/active',过滤器为{completed:false} //如果path为'/completed',过滤器为{completed:true} //否则,过滤器为null $scope.statusFilter=(path==='/active')? {completed:false}:(path==='/completed')? {completed:true}:null; }); //添加一个新的todo $scope.addTodo=function(){ varnewTodo=$scope.newTodo.trim(); if(!newTodo.length){ return; } //向todos里添加一个todo,completed属性默认为false todos.push({ title:newTodo, completed:false }); //置空 $scope.newTodo=''; }; //编辑一个todo $scope.editTodo=function(todo){ $scope.editedTodo=todo; //Clonetheoriginaltodotorestoreitondemand. //保存编辑前的todo,为恢复编辑前做准备 $scope.originalTodo=angular.extend({},todo); }; //编辑todo完成 $scope.doneEditing=function(todo){ //置空 $scope.editedTodo=null; todo.title=todo.title.trim(); if(!todo.title){ //如果todo的title为空,则移除该todo $scope.removeTodo(todo); } }; //恢复编辑前的todo $scope.revertEditing=function(todo){ todos[todos.indexOf(todo)]=$scope.originalTodo; $scope.doneEditing($scope.originalTodo); }; //移除todo $scope.removeTodo=function(todo){ todos.splice(todos.indexOf(todo),1); }; //清除已完成的todos $scope.clearCompletedTodos=function(){ $scope.todos=todos=todos.filter(function(val){ return!val.completed; }); }; //标记所有的todo的状态(true或false) $scope.markAll=function(completed){ todos.forEach(function(todo){ todo.completed=completed; }); }; });