AngularJS学习笔记之TodoMVC的分析


Posted in Javascript onFebruary 22, 2015

最近一段时间一直在看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

/*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。

今天的笔记就先到这里吧,都是些个人心得,希望小伙伴们能够喜欢。

Javascript 相关文章推荐
jquery加载页面的方法(页面加载完成就执行)
Jun 21 Javascript
jquery 插件开发 extjs中的extend用法小结
Jan 04 Javascript
JQueryEasyUI datagrid框架的基本使用
Apr 08 Javascript
3种不同方式的焦点图轮播特效分享
Oct 30 Javascript
5款JavaScript代码压缩工具推荐
Jul 07 Javascript
JavaScript函数定义的常见注意事项小结
Sep 16 Javascript
js随机生成26个大小写字母
Feb 12 Javascript
基于MVC5和Bootstrap的jQuery TreeView树形控件(二)之数据支持json字符串、list集合
Aug 11 Javascript
Vue.js表单控件实践
Oct 27 Javascript
JavaScript实现图片切换效果
Aug 12 Javascript
jquery.pagination.js分页使用教程
Oct 23 jQuery
微信小程序实现点击效果
Jun 21 Javascript
使用jquery组件qrcode生成二维码及应用指南
Feb 22 #Javascript
javascript实现博客园页面右下角返回顶部按钮
Feb 22 #Javascript
JS+CSS实现感应鼠标渐变显示DIV层的方法
Feb 20 #Javascript
js实现图片和链接文字同步切换特效的方法
Feb 20 #Javascript
jQuery实现渐变弹出层和弹出菜单的方法
Feb 20 #Javascript
JavaScript获取文本框内选中文本的方法
Feb 20 #Javascript
jQuery常用数据处理方法小结
Feb 20 #Javascript
You might like
web站点获取用户IP的安全方法 HTTP_X_FORWARDED_FOR检验
2013/06/01 PHP
PHP获取路径和目录的方法总结【必看篇】
2017/03/04 PHP
thinkPHP5.0框架应用请求生命周期分析
2017/03/25 PHP
php使用flock阻塞写入文件和非阻塞写入文件的实例讲解
2017/07/10 PHP
利用PHP_XLSXWriter代替PHPExcel的方法示例
2017/07/16 PHP
来自国外的页面JavaScript文件优化
2010/12/08 Javascript
基于Jquery的动态创建DOM元素的代码
2010/12/28 Javascript
JQuery之拖拽插件实现代码
2011/04/14 Javascript
扩展JavaScript功能的正确方法(译文)
2012/04/12 Javascript
网页加载时页面显示进度条加载完成之后显示网页内容
2012/12/23 Javascript
jQuery 选择器详解
2015/01/19 Javascript
js实现人才网站职位选择功能的方法
2015/08/14 Javascript
JS简单实现多级Select联动菜单效果代码
2015/09/06 Javascript
AngularJS基础 ng-submit 指令简单示例
2016/08/03 Javascript
JS当前页面登录注册框,固定DIV,底层阴影的实例代码
2016/09/29 Javascript
基于js中this和event 的区别(详解)
2017/10/24 Javascript
在ES5与ES6环境下处理函数默认参数的实现方法
2018/05/13 Javascript
bootstrap treeview 树形菜单带复选框及级联选择功能
2018/06/08 Javascript
微信小程序wx.request拦截器使用详解
2019/07/09 Javascript
微信小程序自定义弹出层效果
2020/05/26 Javascript
JS跨浏览器解析XML应用过程详解
2020/10/16 Javascript
python实现识别手写数字 python图像识别算法
2020/03/23 Python
对web.py设置favicon.ico的方法详解
2018/12/04 Python
django xadmin action兼容自定义model权限教程
2020/03/30 Python
使用Python实现将多表分批次从数据库导出到Excel
2020/05/15 Python
通过Python扫描代码关键字并进行预警的实现方法
2020/05/24 Python
浅谈pc和移动端的响应式的使用
2019/01/03 HTML / CSS
德国受欢迎的旅游和休闲网站:lastminute.de
2019/09/23 全球购物
微型企业创业投资计划书
2014/01/10 职场文书
大班开学家长寄语
2014/04/04 职场文书
装修协议书范本
2014/04/21 职场文书
2015年社区中秋节活动总结
2015/03/23 职场文书
PHP命令行与定时任务
2021/04/01 PHP
CSS+HTML 实现顶部导航栏功能
2021/08/30 HTML / CSS
一篇文章带你了解Python和Java的正则表达式对比
2021/09/15 Python
关于对TypeScript泛型参数的默认值理解
2022/07/15 Javascript