AngularJS的学习--TodoMVC的分析
最近一段时间一直在看AngularJS,趁着一点时间总结一下。
先推荐几个教程
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
/*global angular */
/*jshint unused:false */
'use strict'; /**
* The main TodoMVC app module
*
* @type {angular.Module}
*/
var todomvc = angular.module('todomvc', []);
就是定义了一个模块todomvc
再看一下services下的todoStorage.js
/*global todomvc */
'use strict'; /**
* Services that persists and retrieves TODOs from localStorage
*/
todomvc.factory('todoStorage', function () {
// todos JSON字符串存储的唯一标识
var STORAGE_ID = 'todos-angularjs'; return {
// 从localStorage中取出todos,并解析成JSON对象
get: function () {
return JSON.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
/*global todomvc */
'use strict'; /**
* Directive that places focus on the element it is applied to when the expression it binds to evaluates to true
*/
todomvc.directive('todoFocus', function todoFocus($timeout) {
return function (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
/*global todomvc */
'use strict'; /**
* Directive that executes an expression when the element it is applied to gets
* an `escape` keydown event.
*/
todomvc.directive('todoEscape', function () {
var ESCAPE_KEY = 27;
return function (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,这个文件略长,我就直接写注释了。
/*global todomvc, angular */
'use strict'; /**
* The main controller for the app. The controller:
* - retrieves and persists the model via the todoStorage service
* - exposes the model to the template and provides event handlers
*/
todomvc.controller('TodoCtrl', function TodoCtrl($scope, $location, todoStorage, filterFilter) {
// 从localStorage中获取todos
var todos = $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) { // This prevents unneeded calls to the local storage
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 () {
var newTodo = $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;
// Clone the original todo to restore it on demand.
// 保存编辑前的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;
});
};
});
最后看一下index.html,这个文件我们一段一段的分析。
<!doctype html>
<html lang="en" ng-app="todomvc" data-framework="angularjs">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>AngularJS • TodoMVC</title>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css">
<style>[ng-cloak] { display: none; }</style>
</head>
<body>
<section id="todoapp" ng-controller="TodoCtrl">
<header id="header">
<h1>todos</h1>
<form id="todo-form" ng-submit="addTodo()">
<input id="new-todo" placeholder="What needs to be done?" ng-model="newTodo" autofocus>
</form>
</header>
<section id="main" ng-show="todos.length" ng-cloak>
<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-click="markAll(allChecked)">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
<li ng-repeat="todo in todos | filter:statusFilter track by $index" ng-class="{completed: todo.completed, editing: todo == editedTodo}">
<div class="view">
<input class="toggle" type="checkbox" ng-model="todo.completed">
<label ng-dblclick="editTodo(todo)">{{todo.title}}</label>
<button class="destroy" ng-click="removeTodo(todo)"></button>
</div>
<form ng-submit="doneEditing(todo)">
<input class="edit" ng-trim="false" ng-model="todo.title" todo-escape="revertEditing(todo)" ng-blur="doneEditing(todo)" todo-focus="todo == editedTodo">
</form>
</li>
</ul>
</section>
<footer id="footer" ng-show="todos.length" ng-cloak>
<span id="todo-count"><strong>{{remainingCount}}</strong>
<ng-pluralize count="remainingCount" when="{ one: 'item left', other: 'items left' }"></ng-pluralize>
</span>
<ul id="filters">
<li>
<a ng-class="{selected: location.path() == '/'} " href="#/">All</a>
</li>
<li>
<a ng-class="{selected: location.path() == '/active'}" href="#/active">Active</a>
</li>
<li>
<a ng-class="{selected: location.path() == '/completed'}" href="#/completed">Completed</a>
</li>
</ul>
<button id="clear-completed" ng-click="clearCompletedTodos()" ng-show="completedCount">Clear completed ({{completedCount}})</button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Credits:
<a href="http://twitter.com/cburgdorf">Christoph Burgdorf</a>,
<a href="http://ericbidelman.com">Eric Bidelman</a>,
<a href="http://jacobmumm.com">Jacob Mumm</a> and
<a href="http://igorminar.com">Igor Minar</a>
</p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers/todoCtrl.js"></script>
<script src="js/services/todoStorage.js"></script>
<script src="js/directives/todoFocus.js"></script>
<script src="js/directives/todoEscape.js"></script>
</body>
</html>
首先是在最下面,引入相应的JS,这个就不多说了。
<script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers/todoCtrl.js"></script>
<script src="js/services/todoStorage.js"></script>
<script src="js/directives/todoFocus.js"></script>
<script src="js/directives/todoEscape.js"></script>
定义style[ng-cloak],含有ng-cloak属性则不可见。
<style>[ng-cloak] { display: none; }</style>
来看添加todo的html,绑定的model为newTodo,submit的方法是todoCtrl.js中的addTodo(),会添加一条todo,点击Enter,默认触发提交事件,就触发了addTodo()方法,添加了一条todo到todos中。
<form id="todo-form" ng-submit="addTodo()">
<input id="new-todo" placeholder="What needs to be done?" ng-model="newTodo" autofocus>
</form>
再看展示todos的html
<section id="main" ng-show="todos.length" ng-cloak>
<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-click="markAll(allChecked)">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
<li ng-repeat="todo in todos | filter:statusFilter track by $index" ng-class="{completed: todo.completed, editing: todo == editedTodo}">
<div class="view">
<input class="toggle" type="checkbox" ng-model="todo.completed">
<label ng-dblclick="editTodo(todo)">{{todo.title}}</label>
<button class="destroy" ng-click="removeTodo(todo)"></button>
</div>
<form ng-submit="doneEditing(todo)">
<input class="edit" ng-trim="false" ng-model="todo.title" todo-escape="revertEditing(todo)" ng-blur="doneEditing(todo)" todo-focus="todo == editedTodo">
</form>
</li>
</ul>
</section>
section使用ngShow方法根据todos的长度判断是否显示,加上ng-cloak属性是为了在刚开始时不要显示出AngularJS未处理的页面。可以去掉刷新试一试。
其中id为toggle-all的checkbox绑定到allChecked model上,点击触发markAll(allChecked),将allChecked的值传入,标记所有的todos。
使用ngRepeat循环产生li标签,todo in todos | filter:statusFilter track by $index,循环todos,用statusFilter过滤,用$index追踪。ngClass绑定了两个class,{completed: todo.completed, editing: todo == editedTodo},如果todo.completed为true,添加completed class,如果todo==editedTodo,则添加editing class。class为toggle的checkbox绑定到todo.completed。todo标题展示的label绑定了双击事件,双击触发editTodo(todo),editTodo会将todo赋给editedTodo,然后会触发下面form中的todoFocus指令,这时候form中的input可见。按Esc就触发revertEditing(todo),恢复到编辑前,按Enter或者失去焦点就触发doneEditing(todo) ,保存编辑后的todo。class为destroy的button绑定了click事件,点击触发removeTodo(todo),删除掉该条todo。
最后看todos的统计信息展示的html
<footer id="footer" ng-show="todos.length" ng-cloak>
<span id="todo-count"><strong>{{remainingCount}}</strong>
<ng-pluralize count="remainingCount" when="{ one: 'item left', other: 'items left' }"></ng-pluralize>
</span>
<ul id="filters">
<li>
<a ng-class="{selected: location.path() == '/'} " href="#/">All</a>
</li>
<li>
<a ng-class="{selected: location.path() == '/active'}" href="#/active">Active</a>
</li>
<li>
<a ng-class="{selected: location.path() == '/completed'}" href="#/completed">Completed</a>
</li>
</ul>
<button id="clear-completed" ng-click="clearCompletedTodos()" ng-show="completedCount">Clear completed ({{completedCount}})</button>
</footer>
ng-pluralize标签实现了当remainingCount个数为1时,显示 item left,否则显示 items left。
id为filters的ul标签中根据location.path()的内容不同,标记不同的a标签被选中。
id为clear-completed的button添加了点击事件,触发clearCompletedTodos(),清除掉所有已完成的todo。
分析到此结束,如有错误,或者有不明白的地方,请留言~~
AngularJS的学习--TodoMVC的分析的更多相关文章
- 【集美大学1411_助教博客】个人作业2——英语学习APP案例分析 成绩
个人作业2--英语学习APP案例分析,截止发稿时间全班31人,提交31,未提交0人.有一名同学已经写了作业但忘记提交了,这次给分了,但下不为例.由于助教这周有点忙,所以点评得非常不及时,请同学们见谅. ...
- ROS_Kinetic_29 kamtoa simulation学习与示例分析(一)
致谢源代码网址:https://github.com/Tutorgaming/kamtoa-simulation kamtoa simulation学习与示例分析(一) 源码学习与分析是学习ROS,包 ...
- GIS案例学习笔记-水文分析河网提取地理建模
GIS案例学习笔记-水文分析河网提取地理建模 联系方式:谢老师,135-4855-4328,xiexiaokui#qq.com 目的:针对数字高程模型,通过水文分析,提取河网 操作时间:25分钟 数据 ...
- Android:日常学习笔记(2)——分析第一个Android应用程序
Android:日常学习笔记(2)——分析第一个Android应用程序 Android项目结构 整体目录结构分析 说明: 除了APP目录外,其他目录都是自动生成的.APP目录的下的内容才是我们的工作重 ...
- 【js类库AngularJs】学习angularJs的指令(包括常见表单验证,隐藏等功能)
[js类库AngularJs]学习angularJs的指令(包括常见表单验证,隐藏等功能) AngularJS诞生于2009年,由Misko Hevery 等人创建,后为Google所收购.是一款优秀 ...
- HashMap的源码学习以及性能分析
HashMap的源码学习以及性能分析 一).Map接口的实现类 HashTable.HashMap.LinkedHashMap.TreeMap 二).HashMap和HashTable的区别 1).H ...
- Netty 源码学习——客户端流程分析
Netty 源码学习--客户端流程分析 友情提醒: 需要观看者具备一些 NIO 的知识,否则看起来有的地方可能会不明白. 使用版本依赖 <dependency> <groupId&g ...
- 深度学习Dropout技术分析
深度学习Dropout技术分析 什么是Dropout? dropout是指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率将其暂时从网络中丢弃.注意是暂时,对于随机梯度下降来说,由于是随机 ...
- AngularJS系统学习之Scope(作用域)
本文出自:https://www.w3ctech.com/topic/1611 看完了没怎么懂, 也许是和别人 原文作者: Nicolas Bhttps://www.w3ctech.com/topi ...
随机推荐
- Python:字符编码详解
相关文章 Python中文编码问题:为何在控制台下输出中文会乱码及其原理 1. 字符编码简介 1.1. ASCII ASCII(American Standard Code for Informati ...
- ES5 数组方法every和some
Array.prototype.every() 概述 every() 方法测试数组的所有元素是否都通过了指定函数的测试. 语法 arr.every(callback[, thisArg]) 参数 ca ...
- #入魔这些年#零度智控&模型控社区大型征文活动,万元大奖等你拿
玩航模是个好爱好,它能培养严谨的科学态度.超强的DIY动手能力.规范的项目管理习惯.良好的沟通合作技巧.过人的三维空间思维和感知: 你需要的知识储备有力学.空气动力学.电子学.通讯工程.材料学 ...
- JavaScript学习汇总
对于JavaScript,还是无法割舍,有心无力,时间总是匆匆,暂且都放在这里吧 javascript中this的使用 写的很不错的一偏文章,简单看了下,mark了吧 原文:http://davids ...
- Erwin 生成 mysql 带注释(comment )的脚本
Erwin设计数据库非常方便,有逻辑视图和物理视图,可以很方便的生成数据库文档和SQL 脚本.在使用过程中唯一不爽的地方是脚本不能生成comment. 在百度无数次无法解决下,又FQ谷歌,在一个日 ...
- Windows下修改Oracle默认的端口1521
数据库最好不对公网开放,如果要开放,最好把默认端口改掉,防止一些针对 1521端口的入侵 1.找到 product\11.2.0\dbhome_1\NETWORK\ADMIN 下面的 listene ...
- 第一篇HBuilder在这里!
本文作为一个引子,所有相关文章,都会在这里做一个索引,现在还空着.谢谢
- Android-NDK编译:cocos2d-x(二)
看了看NDK的文档....里面总是莫名的会提下windows需要cygwin环境... 但是cocos2d-x3.0alpha1 中, 编译samples的python脚本 cocos2d-x3/bu ...
- ImageSource的使用
很多时候,我们会使用图片来装饰UI,比如作为控件背景等.而这些图片可以分为两种形式,即存在于本地文件系统中的图片和存在于内存中的图片对于这两种形式的图片,在WPF中,使用方法不同,下面主要说明针对这两 ...
- Linux网络流量实时监控ifstat iftop命令详解
ifstat 介绍 ifstat工具是个网络接口监测工具,比较简单看网络流量 实例 默认使用 #ifstat eth0 eth1 KB /s i ...