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 相关文章推荐
用js实现控制内容的向上向下滚动效果
Jun 26 Javascript
jQuery.Validate 使用笔记(jQuery Validation范例 )
Jun 25 Javascript
表头固定(利用jquery实现原理介绍)
Nov 08 Javascript
JS字符串处理实例代码
Aug 05 Javascript
jquery.ajax之beforeSend方法使用介绍
Dec 08 Javascript
jQuery性能优化技巧分析
Feb 20 Javascript
JavaScript分页功能的实现方法
Apr 25 Javascript
基于Javascript实现二级联动菜单效果
Mar 04 Javascript
JavaScript中的Reflect对象详解(ES6新特性)
Jul 22 Javascript
Angularjs使用过滤器完成排序功能
Sep 20 Javascript
Node.js npm命令运行node.js脚本的方法
Oct 10 Javascript
node.js实现带进度条的多文件上传
Mar 27 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
超外差式晶体管收音机的组装与统调
2021/03/01 无线电
几个php应用技巧
2008/03/27 PHP
php初学者写及时补给skype用户充话费的小程序
2008/11/02 PHP
PHP Zip压缩 在线对文件进行压缩的函数
2010/05/26 PHP
解读PHP中的垃圾回收机制
2015/08/10 PHP
php生成高清缩略图实例详解
2015/12/07 PHP
PHP基于SMTP协议实现邮件发送实例代码
2017/04/27 PHP
详解如何在云服务器上部署Laravel
2017/06/30 PHP
TNC vs BOOM BO3 第三场2.13
2021/03/10 DOTA
一个加载js文件的小脚本
2007/06/28 Javascript
JS 密码强度验证(兼容IE,火狐,谷歌)
2010/03/15 Javascript
IE6/IE7中JavaScript json提示缺少标识符、字符串或数字问题处理
2014/12/16 Javascript
使用HTML+CSS+JS制作简单的网页菜单界面
2015/07/27 Javascript
浅谈jQuery添加的HTML,JS失效的问题
2016/10/05 Javascript
jQuery 控制文本框自动缩小字体填充
2017/06/16 jQuery
深入理解vue Render函数
2017/07/19 Javascript
Javascript格式化并高亮xml字符串的方法及注意事项
2018/08/13 Javascript
jQuery高级编程之js对象、json与ajax用法实例分析
2019/11/01 jQuery
京东优选小程序的实现代码示例
2020/02/25 Javascript
python中定义结构体的方法
2013/03/04 Python
Python中多线程thread与threading的实现方法
2014/08/18 Python
Python异常学习笔记
2015/02/03 Python
使用Python制作获取网站目录的图形化程序
2015/05/04 Python
centos 安装Python3 及对应的pip教程详解
2019/06/28 Python
解决Django连接db遇到的问题
2019/08/29 Python
浅谈Python_Openpyxl使用(最全总结)
2019/09/05 Python
Python如何计算语句执行时间
2019/11/22 Python
python实现拼图小游戏
2020/02/22 Python
Python 用__new__方法实现单例的操作
2020/12/11 Python
HTML5 本地存储 LocalStorage详解
2016/06/24 HTML / CSS
玛蒂尔达简服装:Matilda Jane Clothing
2019/02/13 全球购物
软件配置管理有什么好处
2015/04/15 面试题
哈理工毕业生的求职信
2013/12/22 职场文书
文明好少年事迹材料
2014/08/19 职场文书
春秋淹城导游词
2015/02/11 职场文书
Pytest allure 命令行参数的使用
2021/04/18 Python