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 弹出菜单/窗口效果
Oct 30 Javascript
JavaScript中OnLoad几种使用方法
Dec 15 Javascript
网页中返回顶部代码(多种方法)另附注释说明
Apr 24 Javascript
基于jquery插件实现常见的幻灯片效果
Nov 01 Javascript
JQuery文字列表向上滚动的代码
Nov 13 Javascript
用C/C++来实现 Node.js 的模块(二)
Sep 24 Javascript
浅谈javascript面向对象程序设计
Jan 21 Javascript
JS实现的驼峰式和连字符式转换功能分析
Dec 21 Javascript
JS正则RegExp.test()使用注意事项(不具有重复性)
Dec 28 Javascript
Node.js Mongodb 密码特殊字符 @的解决方法
Apr 11 Javascript
纯js实现隔行变色效果
Nov 29 Javascript
原生Javascript+HTML5一步步实现拖拽排序
Jun 12 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
smtp邮件发送一例
2006/10/09 PHP
javascript动态改变img的src属性图片不显示的解决方法
2010/10/20 Javascript
jQuery setTimeout()函数使用方法
2013/04/07 Javascript
Javascript计算两个marker之间的距离(Google Map V3)
2013/04/26 Javascript
js控制表单奇偶行样式的简单方法
2013/07/31 Javascript
jquery下拉select控件操作方法分享(jquery操作select)
2014/03/25 Javascript
jquery实现多行文字图片滚动效果示例代码
2014/10/10 Javascript
javascript的函数作用域
2014/11/12 Javascript
javascript手工制作悬浮菜单
2015/02/12 Javascript
JS实现图片产生波纹一样flash效果的方法
2015/02/27 Javascript
移除AngularJS下URL中的#字符的方法
2015/06/19 Javascript
纯JS代码实现一键分享功能
2016/04/20 Javascript
js实现点击图片自动提交action的简单方法
2016/10/16 Javascript
BootStrap表单宽度设置方法
2017/03/10 Javascript
JS简单获取当前日期时间的方法(如:2017-03-29 11:41:10 星期四)
2017/03/29 Javascript
基于Vue2x实现响应式自适应轮播组件插件VueSliderShow功能
2018/05/16 Javascript
微信小程序实现人脸识别
2018/05/25 Javascript
详解Vue webapp项目通过HBulider打包原生APP(vue+webpack+HBulider)
2019/02/02 Javascript
vue各种事件监听实例(小结)
2020/06/24 Javascript
mustache.js实现首页元件动态渲染的示例代码
2020/12/28 Javascript
python编写暴力破解FTP密码小工具
2014/11/19 Python
详解Python中的循环语句的用法
2015/04/09 Python
在Python中操作字典之clear()方法的使用
2015/05/21 Python
Numpy掩码式数组详解
2018/04/17 Python
python实现三维拟合的方法
2018/12/29 Python
学会迭代器设计模式,帮你大幅提升python性能
2021/01/03 Python
使用CSS3编写灰阶滤镜来制作黑白照片效果的方法
2016/05/09 HTML / CSS
使用HTML5中的contentEditable来将多行文本自动增高
2016/03/01 HTML / CSS
小程序canvas中文字设置居中锚点
2019/04/16 HTML / CSS
机械设计制造专业个人求职信
2013/09/25 职场文书
艺术爱好者的自我评价分享
2013/10/08 职场文书
《故都的秋》教学反思
2014/04/15 职场文书
《歌唱二小放牛郎》教学反思
2014/04/19 职场文书
中学生国旗下讲话稿
2014/04/26 职场文书
2015年医德医风工作总结
2015/04/02 职场文书
利用Python网络爬虫爬取各大音乐评论的代码
2021/04/13 Python