AngularJS學(xué)習(xí)筆記之TodoMVC的分析
最近一段時(shí)間一直在看AngularJS,趁著一點(diǎn)時(shí)間總結(jié)一下。
官網(wǎng)地址:http://angularjs.org/
先推薦幾個教程
1. AngularJS入門教程 比較基礎(chǔ),是官方Tutorial的翻譯。
2. 七步從AngularJS菜鳥到專家 也比較基礎(chǔ),制作了一個在線音樂播放網(wǎng)站。
3. AngularJS開發(fā)指南 這個教程比較全面,但我感覺翻譯的有些晦澀難懂。
看過這些教程后,覺得AngularJS也懂一點(diǎn)了,就想用它干點(diǎn)事,就分析一下AngularJS寫的todomvc吧。
Todomvc官網(wǎng)地址:http://todomvc.com/
項(xiàng)目的目錄如下:
bower_components里放了兩個文件夾,其中angular文件夾是用來一如angular.js文件的,todomvc-common文件夾里的放入了所有todo項(xiàng)目統(tǒng)一的css\js(只是用來生成左側(cè)內(nèi)容的,與項(xiàng)目無關(guān))和圖片。
js文件夾是大頭,里面放了相應(yīng)的controller(控制器)\directive(指令)\service(服務(wù))和app.js。
test文件夾里放的是測試用的代碼,不分析。
index.html是項(xiàng)目的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字符串存儲的唯一標(biāo)識
var STORAGE_ID = 'todos-angularjs';
return {
// 從localStorage中取出todos,并解析成JSON對象
get: function () {
return JSON.parse(localStorage.getItem(STORAGE_ID) || '[]');
},
// 將todos對象轉(zhuǎn)化成JSON字符串,并存入localStorage
put: function (todos) {
localStorage.setItem(STORAGE_ID, JSON.stringify(todos));
}
};
});
使用factory方法創(chuàng)建了todoStorage的service方法,這個service方法的本質(zhì)就是返回了兩個方法get和put,兩者都是用了JSON2和HTML5的特性。get將todos的內(nèi)容從localStorage中取出,并解析成JSON,put將todos轉(zhuǎn)化成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屬性的值添加監(jiān)聽
scope.$watch(attrs.todoFocus, function (newVal) {
if (newVal) {
$timeout(function () {
elem[0].focus();
}, 0, false);
}
});
};
});
返回function的參數(shù)中,elem就是包含該指令的元素的數(shù)組,attrs是元素的所有屬性、屬性名等組成的對象。
其中用到了兩個AngularJS的方法
$watch(watchExpression, listener, objectEquality) 注冊一個偵聽器回調(diào),每當(dāng)watchExpression變化時(shí),監(jiān)聽回調(diào)將被執(zhí)行。
$timeout(fn[, delay][, invokeApply]) 當(dāng)timeout的值達(dá)到時(shí),執(zhí)行fn函數(shù)。
todoFocus.js創(chuàng)建了todoFocus指令。當(dāng)一個元素?fù)碛衪odoFocus屬性時(shí),該指令會為該元素的todoFocus屬性的值添加監(jiān)聽,如果todoFocus屬性的值改變成true,就會執(zhí)行$timeout(function () {elem[0].focus();}, 0, false);其中的延遲時(shí)間為0秒,所以會立即執(zhí)行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創(chuàng)建了todoEscape指令。當(dāng)按下Escape鍵時(shí),執(zhí)行attrs.todoEscape的表達(dá)式。
看一下大頭,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;
// 當(dāng)todos的值改變時(shí)執(zhí)行其中的方法
$scope.$watch('todos', function (newValue, oldValue) {
// 獲取未完成的todos的數(shù)目
$scope.remainingCount = filterFilter(todos, { completed: false }).length;
// 獲取已完成的todos的數(shù)目
$scope.completedCount = todos.length - $scope.remainingCount;
// 當(dāng)且僅當(dāng)$scope.remainingCount為0時(shí),$scope.allChecked為true
$scope.allChecked = !$scope.remainingCount;
// 當(dāng)todos的新值和舊值不相等時(shí),向localStorage中存入todos
if (newValue !== oldValue) { // This prevents unneeded calls to the local storage
todoStorage.put(todos);
}
}, true);
if ($location.path() === '') {
// 如果$location.path()為空,就設(shè)置為/
$location.path('/');
}
$scope.location = $location;
// 當(dāng)location.path()的值改變時(shí)執(zhí)行其中的方法
$scope.$watch('location.path()', function (path) {
// 獲取狀態(tài)的過濾器
// 如果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屬性默認(rèn)為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,為恢復(fù)編輯前做準(zhǔn)備
$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);
}
};
// 恢復(fù)編輯前的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;
});
};
// 標(biāo)記所有的todo的狀態(tài)(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=" Burgdorf</a>,
<a href=" Bidelman</a>,
<a href=" Mumm</a> and
<a href=" Minar</a>
</p>
<p>Part of <a href=">
</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>
首先是在最下面,引入相應(yīng)的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,點(diǎn)擊Enter,默認(rèn)觸發(fā)提交事件,就觸發(fā)了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方法根據(jù)todos的長度判斷是否顯示,加上ng-cloak屬性是為了在剛開始時(shí)不要顯示出AngularJS未處理的頁面。可以去掉刷新試一試。
其中id為toggle-all的checkbox綁定到allChecked model上,點(diǎn)擊觸發(fā)markAll(allChecked),將allChecked的值傳入,標(biāo)記所有的todos。
使用ngRepeat循環(huán)產(chǎn)生li標(biāo)簽,todo in todos | filter:statusFilter track by $index,循環(huán)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標(biāo)題展示的label綁定了雙擊事件,雙擊觸發(fā)editTodo(todo),editTodo會將todo賦給editedTodo,然后會觸發(fā)下面form中的todoFocus指令,這時(shí)候form中的input可見。按Esc就觸發(fā)revertEditing(todo),恢復(fù)到編輯前,按Enter或者失去焦點(diǎn)就觸發(fā)doneEditing(todo) ,保存編輯后的todo。class為destroy的button綁定了click事件,點(diǎn)擊觸發(fā)removeTodo(todo),刪除掉該條todo。
最后看todos的統(tǒng)計(jì)信息展示的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標(biāo)簽實(shí)現(xiàn)了當(dāng)remainingCount個數(shù)為1時(shí),顯示 item left,否則顯示 items left。
id為filters的ul標(biāo)簽中根據(jù)location.path()的內(nèi)容不同,標(biāo)記不同的a標(biāo)簽被選中。
id為clear-completed的button添加了點(diǎn)擊事件,觸發(fā)clearCompletedTodos(),清除掉所有已完成的todo。
今天的筆記就先到這里吧,都是些個人心得,希望小伙伴們能夠喜歡。
相關(guān)文章
Angularjs自定義指令實(shí)現(xiàn)三級聯(lián)動 選擇地理位置
這篇文章主要介紹了Angularjs自定義指令實(shí)現(xiàn)三級聯(lián)動,選擇地理位置,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02AngularJS使用ng-app自動加載bootstrap框架問題分析
這篇文章主要介紹了AngularJS使用ng-app自動加載bootstrap框架問題,分析了前面文章中所述的ng-app自動加載bootstrap出現(xiàn)的錯誤原因與相應(yīng)的解決方法,需要的朋友可以參考下2017-01-01Angular 數(shù)據(jù)請求的實(shí)現(xiàn)方法
本篇文章主要介紹了Angular 數(shù)據(jù)請求的實(shí)現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05Angular使用 ng-img-max 調(diào)整瀏覽器中的圖片的示例代碼
本篇文章主要介紹了Angular使用 ng-img-max 調(diào)整瀏覽器中的圖片的示例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08Angular6 用戶自定義標(biāo)簽開發(fā)的實(shí)現(xiàn)方法
這篇文章主要介紹了Angular6 用戶自定義標(biāo)簽開發(fā)的實(shí)現(xiàn)方法,下面我們就通過一個簡單的例子演示Angular6中的這一新功能,小編覺得挺不錯的,現(xiàn)在分享給大家,需要的朋友可以參考下2019-01-01