前言:記得三月份時下定決心說每天要更新一篇博客,學習點新東西,實踐下來發現太不現實,生活中的事情很多,再喜歡也不能讓它一件占據生活的全部吧,所以呢,以後順其自然吧。之前有一篇‘初識angular’因為離職找工作等一系列原因,擱置了好久,今早看看,繼續寫以前的已經無法繼續,索性重新開始,有時間再修該之 ...
前言:記得三月份時下定決心說每天要更新一篇博客,學習點新東西,實踐下來發現太不現實,生活中的事情很多,再喜歡也不能讓它一件占據生活的全部吧,所以呢,以後順其自然吧。之前有一篇‘初識angular’因為離職找工作等一系列原因,擱置了好久,今早看看,繼續寫以前的已經無法繼續,索性重新開始,有時間再修該之前的吧。
二識angular(基於angular官方文檔)
地址:https://angularjs.org/
一,基礎:先看html代碼
<!doctype html> <html ng-app><!--ng-app聲明頁面的這個部分將基於angular--> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script> </head> <body> <div> <label>Name:</label> <input type="text" ng-model="yourName" placeholder="Enter a name here"> <!--ng-model將表單和模型聯繫在一起,這裡的即yourname,這樣你在表單中輸入的內容將出現在後文中調用該變數的地方,{{yourName}}--> <h1>Hello {{yourName}}!</h1> </div> </body> </html>
構建angular應用,首先應該應該引入資源文件,其次,應該聲明,也就是ng-app指令,告訴應用它是基於angular的。第三,angular是通過各種指令來實現的,因此,第三個步驟就是學習各種指令。
上面代碼中我們用到了一個ng-mode指令,l它將表單和模型聯繫在一起,能夠在應用中‘瞬間’獲得表單輸入的內容。比如,上面的代碼,你在表單中輸入的內容會立刻出現在hello的後面;而且,無論yourName變數何時發生改變它的所有引用也會立即更新。
二,加入一些控制
第一步,我們實現了一個簡單的數據的雙向綁定,但是,實際的應用場景中,情況比這複雜的多,很所時候,我們需要收集,判斷,處理等等。因此,我們需要加入一些控制。
數據綁定:data-binding是一種無論model何時改變都會自動更新的方式,就如上文講到的‘瞬間’獲得;同時,他也會在視圖發生改變時更新model。這一點讓煩人的dom操作成了一件你不在需要擔心的事。($scope.$apply();這段代碼暫且放在這裡,它的作用之一就是可以傳播model的變化,通常情況下,我們在頁面中直接加入的ng-model是可以“瞬間”更新的,但是如果是在jQuery的ajax中,並不是瞬間實現數據同步的,我們需要將改變傳播出去,這樣才可以起作用。但是,並不提倡濫用這個方法,一般angular JS自帶的方法會預設調用該方法。)
控制器:controller,暫且叫它控制器吧,它是和dom元素相關的一系列行為,它讓你將這些行為用乾凈可讀性強的表單來表達,取代以前的樣板式的通過註冊回調函數,或者監聽模型是否發生改變來更新form。
簡單的原生JavaScript:不同於其他框架,angular是簡單的JavaScript項目,你不需要有專門的樣板或者調用及繼承來讓你的東西匹配angular,這一點讓你的代碼很容易測試,維護,重用,而且從複雜的樣板式調用中解放出來。
下麵看一個可以添加未做事項清單的小應用;
html代碼:
<!doctype html> <html ng-app="todoApp"> <head> <meta charset="utf-8" /> <script type="text/javascript" src="js/angular.min.js"></script> <script type="text/javascript" src="js/todo.js"></script> <style> .done-true {text-decoration: line-through;color: grey;} ul,li{list-style: none;} </style> </head> <body> <h2>需要做的事情清單</h2> <!--這個控制器內部的元素行為將被由ng-controller這個命令所指定的控制器TodoListController所控制。--> <div ng-controller="TodoListController as todoList"> <!--{{todoList.todos.length}}{{todoList.todos.length}}數據調用--> <span>您共有{{todoList.todos.length}}件需要做的事情,其中的{{todoList.todos.length}}項還未完成</span> [ <!--ng-click="todoList.archive()"這也是一個方法調用,顯示還未完成的清單列表--> <a href="" ng-click="todoList.archive()">僅顯示未完成清單</a> ] <ul> <li ng-repeat="todo in todoList.todos"> <label class="checkbox"> <input type="checkbox" ng-model="todo.done"> <span class="done-{{todo.done}}">{{todo.text}}</span> </label> </li> </ul> <!--調用方法ng-submit="todoList.addTodo()"--> <form ng-submit="todoList.addTodo()"> <input type="text" ng-model="todoList.todoText" size="30" placeholder="添加你將要做的事情"> <input class="btn-primary" type="submit" value="添加"> </form> </div> </body> </html>
(1),ng-controller="TodoListController as todoList"。
註意這個as,後文我們調用TodoListController 時只需要使用todolist代替就好了,如:
ng-submit="todoList.addTodo()
(2),ng-repeat="todo in todoList.todos"。
將 todoList.todos用todo來指代,後文可直接todo.xxx的形式來調用todos裡面的數據。
(3), <span class="done-{{todo.done}}">{{todo.text}}</span>。
類名中一樣可以使用表達式,並且包含在雙引號內部。
.done-true { text-decoration: line-through; color: grey; }
(4),ng-click="todoList.archive()。
事件調用。
註意看這個方法,語法幾乎就是JavaScript,只是有時候加上了一些屬於angular的方法。比如本例中的angular.forEach()。angular中可以直接使用JavaScript。
//定義控制器的方法archive,顯示清單庫中所有還未完成的事情。 todoList.archive = function() { var oldTodos = todoList.todos; todoList.todos = []; angular.forEach(oldTodos, function(todo) { if(!todo.done) todoList.todos.push(todo);//如果事件未完成,則加入事件清單裡面; }); };
js代碼:定義了一個模塊todoApp,同時給這個模塊添加了一組數據和三個方法。
angular.module('todoApp', []) //所有的行為控制寫在控制器裡面; .controller('TodoListController', function() { var todoList = this;//定義當前控制器的對象; todoList.todos = [{//數據源 text: '學習 AngularJS', done: true }, { text: '建一個 AngularJS app', done: false } ]; //定義控制器的方法addTodo,添加未完成的事情。 todoList.addTodo = function() { todoList.todos.push({ text: todoList.todoText, done: false }); todoList.todoText = ''; }; //定義控制器的方法remaining,還未完成的事情數目 todoList.remaining = function() { var count = 0; angular.forEach(todoList.todos, function(todo) { count += todo.done ? 0 : 1; }); return count; }; //定義控制器的方法archive,顯示清單庫中所有還未完成的事情。 todoList.archive = function() { var oldTodos = todoList.todos; todoList.todos = []; angular.forEach(oldTodos, function(todo) { if(!todo.done) todoList.todos.push(todo); }); }; });
三,和後臺交互
深鏈接(deep link):一個深鏈接,反應出用戶此刻處在APP的那個流程中,這對於用戶將頁面存為書簽和發送郵件很方便,往返型的APP可以自動獲得以上這些,但是ajax類型的應用,則不可能實現這些。angular JS將深鏈接和桌面應用的類APP行為結合起來。
表單驗證(Form Validation):客戶端的表單驗證是一個好的用戶體驗中很重要的一個版塊。angularJS讓你不用寫JavaScript代碼就可以聲明驗證規則。通過類名結合布爾值來判斷,實現表單的驗證。
服務端交互(Server Communication):angular JS提供嵌入的服務,這些服務基於XHR和其他很多不同種類的第三方庫提供的後臺。promises能夠很好地簡化你的代碼,通過管理非同步的回調數據。下麵的例子,我們將通過AngularJS 的AngularFire 庫來為一個簡單的angular JS APP搭建一個Firebase後臺。
先看代碼:
主頁面,主要載入資源文件和提供顯示視圖的容器,ng-view可以結合路由配置實現同一個頁面內部不同子內容的載入。不同的路由配置下,載入不同的模板。
<!doctype html> <html ng-app="project"> <head> <meta charset="UTF-8"> <link rel="stylesheet" type="text/css" href="css/angular.css"/> <link rel="stylesheet" href="css/bootstrap.min.css" /> <link rel="stylesheet" href="css/font-awesome.css" /> <script type="text/javascript" src="js/jquery-1.9.1.min.js" ></script> <script type="text/javascript" src="js/bootstrap.min.js" ></script> <script type="text/javascript" src="js/angular.min.js" ></script> <script type="text/javascript" src="js/angular-resource.min.js" ></script> <script type="text/javascript" src="js/angular-route.min.js" ></script> <script src="https://cdn.firebase.com/js/client/2.0.4/firebase.js"></script> <script src="https://cdn.firebase.com/libs/angularfire/0.9.0/angularfire.min.js"></script> <script type="text/javascript" src="js/project.js" ></script> <script type="text/javascript" src="js/project-list.js" ></script> </head> <body> <div class="container"> <h2>JavaScript Projects</h2> <!--我們通過這個div,作為載入局部頁面或者視圖的地方。它周圍的頁面會保持靜態, 當我們在這個模塊中動態載入時,這樣我們可以在一系列對象和表單之間切換, 來添加新的項目或者編輯已經存在的項目。--> <!--動態載入不同的子內容,通過路由配置結合模板來實現,這之外的部分,保持靜態--> <div ng-view></div> </div> </body> </html>
列表模板:通過遍歷取到的數據,生成列表。
<!--ng-model="projectList.search",將輸入域和search屬性綁定,這個選擇器用來選擇只包含用戶輸入的關鍵字的對象。--> <input type="text" ng-model="projectList.search" class="search-query" id="projects_search" placeholder="Search"> <table> <thead> <!--表頭--> <tr> <th>Project</th> <th>Description</th> <th> <!--一個連接到/new的路由,這個路由已經在project.js中配置過, 既然我們遵循web的精神,沒有必要在鏈接上註冊回調函數,我們只是簡單的導航到一個新的路由 它會自動更新瀏覽器的瀏覽歷史,並且使deep-linking可用。 但是,不同於普通的server round trip application應用(伺服器往返應用),這個navigation event會在瀏覽器中被立即渲染--> <a href="#!/new"><i class="icon-plus-sign"></i></a> </th> </tr> </thead> <!--表格主體--> <tbody> <!--ng-repeat="project in projectList.projects | filter:projectList.search | orderBy:'name'" ng-repeat用來展開一個數據集合,(遍歷),對於集合中的每一條數據,angular都會執行一次生成操作 --> <!--filter,用來選擇一個集合的子集,該子集是包含projectList.search關鍵字的一個集合。 orderBy,指定子集的排序規則--> <tr ng-repeat="project in projectList.projects | filter:projectList.search | orderBy:'name'"> <!--遍歷生成表格的每一行--> <td> <a ng-href="{{project.site}}" target="_blank">{{project.name}}</a> </td> <td>{{project.description}}</td> <td> <a ng-href="#!/edit/{{project.$id}}"><i class="icon-pencil"></i></a> </td> </tr> </tbody> </table>
修改或者添加模板:重點是表單驗證。
<!--創建一個名為myForm的表單,在這裡我們會聲明驗證規則,來顯示錯誤輸入和不可操作的表單--> <form name="myForm"> <!--添加一個error類,當輸入不合法時,$pristine意思是如果表單未被使用,也就是輸入為空。--> <div class="control-group" ng-class="{error: myForm.name.$invalid && !myForm.name.$pristine}"> <label>Name</label> <!--required:當沒有輸入時,將輸入域置為無效--> <input type="text" name="name" ng-model="editProject.project.name" required> <!--show這個錯誤信息,當input name有requeired error時--> <span ng-show="myForm.name.$error.required && !myForm.name.$pristine" class="help-inline"> Required {{myForm.name.$pristine}}</span> </div> <div class="control-group" ng-class="{error: myForm.site.$invalid && !myForm.site.$pristine}"> <label>Website</label> <input type="url" name="site" ng-model="editProject.project.site" required> <span ng-show="myForm.site.$error.required && !myForm.site.$pristine" class="help-inline"> Required</span> <span ng-show="myForm.site.$error.url" class="help-inline">Not a URL</span> </div> <label>Description</label> <textarea name="description" ng-model="editProject.project.description"></textarea> <br> <a href="#!/" class="btn">Cancel</a> <!--ng-disabled 將save按鈕置為不可操作,當表單沒有輸入或者輸入有誤時--> <button ng-click="editProject.save()" ng-disabled="myForm.$invalid" class="btn btn-primary">Save</button> <button ng-click="editProject.destroy()" ng-show="editProject.project.$id" class="btn btn-danger">Delete</button> </form>
JavaScript文件:
項目主代碼:project.js。主要包括取數據,路由配置和顯示列表、添加項目、修改項目三個controller的定義。(註意裡面的各種註入的依賴,在function裡面聲明)
//定義module,通過它你可以安裝(載入)angular已有的服務和定義新的命令,服務和選擇器等等。 angular.module('project', ['ngRoute', 'firebase']) //模塊可以依賴於其它模塊,這裡project需要firebase來處理這個應用的可持續。 .value('fbURL', 'https://ng-projects-list.firebaseio.com/') //.value可以用來定義一個單獨的對象,可以註入到其它的controllers和servises中去。前面是變數名,後面是值。 .service('fbRef', function(fbURL) { return new Firebase(fbURL)//返回請求地址 }) .service('fbAuth', function($q, $firebase, $firebaseAuth, fbRef) {//$firebase firegase提供的一個服務 var auth; return function() { if(auth) return $q.when(auth); var authObj = $firebaseAuth(fbRef); if(authObj.$getAuth()) { return $q.when(auth = authObj.$getAuth()); } var deferred = $q.defer(); authObj.$authAnonymously().then(function(authData) { auth = authData; deferred.resolve(authData); }); return deferred.promise; } }) /** * Projects是firebase的一個實例,在project模塊中已經定義過了, * 它提供對應用進行增加,刪除和更新的方法(介面), * 它的目標是將伺服器交互抽象化。 * 它讓controllers集中處理行為而不是複雜的伺服器連接之類。 **/ .service('Projects', function($q, $firebase, fbRef, fbAuth, projectListValue) { var self = this; this.fetch = function() {//取數據,也就是和後臺交互。這裡是基於firebase,也可以是通過其他形式來進行數據調用,比如ajax if(this.projects) return $q.when(this.projects); return fbAuth().then(function(auth) { var deferred = $q.defer(); var ref = fbRef.child('projects-fresh/' + auth.auth.uid); var $projects = $firebase(ref); ref.on('value', function(snapshot) { if(snapshot.val() === null) { //我們可以通過將一個對象的值設為null來刪除它。 $projects.$set(projectListValue); } //$asArray()一個方法,以數組形式返回firebase裡面的數據。 self.projects = $projects.$asArray();//取到數據並以數組形式返回 deferred.resolve(self.projects); }); //Remove projects list when no longer needed. ref.onDisconnect().remove(); return deferred.promise; }); }; }) /* * .config()可以用來對已經存在的服務進行配置, * 這裡我們將對$routeProvider進行配置,來讓它適用於局部路徑。 * */ .config(function($routeProvider) { var resolveProjects = { projects: function(Projects) { return Projects.fetch();//獲取數據,依賴於Projects模塊 } }; $routeProvider /** * '/'當URL是/的時候,將會載入List.html到view里,同時和ProjectListController相關聯, * 通過閱讀路由定義,你可以立即得到一個關於APP結構的概覽。 * **/ .when('/', { /** * controller定義一個controller function可以和使用ng-congroller的dom元素關聯 * 或者和一個view template通過在路由配置裡面指定從而實現關聯。 * **/ controller: 'ProjectListController as projectList',//聲明controller templateUrl: 'list.html', resolve: resolveProjects//數據調用 }) /** * '/edit/:projectId',這個路由定義我們使用了冒號,我們可以通過冒號來讓URL的一個部分可以被controller調用。(類似於傳參吧) * 現在,edit controller可以使用projectId作為參數,來找到需要edit的對象。 * **/ .when('/edit/:projectId', { controller: 'EditProjectController as editProject', templateUrl: 'detail.html', resolve: resolveProjects }) .when('/new', { controller: 'NewProjectController as editProject', templateUrl: 'detail.html', resolve: resolveProjects }) //.otherwise()指定,噹噹前路由不滿足已經配置的所有路由時要顯示的界面。 .otherwise({ redirectTo: '/' }); }) //顯示列表 .controller('ProjectListController', function(projects) { var projectList = this; projectList.projects = projects;//把數據源導入 }) //新增項目 //可以通過$location服務,來使用瀏覽器的location對象 .controller('NewProjectController', function($location, projects) { var editProject = this; //當視圖中的save按鈕被點擊時,執行 editProject.save = function() { projects.$add(editProject.project).then(function(data) { /** * 我們用.path()方法來改變location的'deep-linking'location。 * URL的改變,會立即激活新的路由,並且讓應用顯示對應的view,這裡也就是 * **/ $location.path('/');//添加之後跳轉到預設的列表頁 }); }; }) //修改項目 .controller('EditProjectController', //$routeParams:這裡我們讓angular註入$routeParams,通過它來使從路由配置中提取出來的參數可用。 function($location, $routeParams, projects) { var editProject = this; //projectId:提取URL中的projectId,它允許controller利用應用的deep-linking信息進行加工。(生成其它內容) var projectId = $routeParams.projectId, projectIndex; editProject.projects = projects; projectIndex = editProject.projects.$indexFor(projectId); editProject.project = editProject.projects[projectIndex]; //當用戶單擊刪除按鈕時執行。 editProject.destroy = function() { editProject.projects.$remove(editProject.project).then(function(data) { $location.path('/');//刪除之後跳轉到主頁。也就是預設的列表頁 }); }; editProject.save = function() { editProject.projects.$save(editProject.project).then(function(data) { $location.path('/');//保存之後跳轉到預設的列表頁 }); }; });
要點分析:
(1),angular.module:這個語句定義一個angular模塊或者說是小的‘應用’,是一個相對獨立的單元,它可以擁有自己的一系列‘私有物’。
(2),angular.module.value:定義一個獨立的angular對象,他可以註入到其它的controllers或者service中去,我的理解是類似於js中的靜態變數的感覺。
(3),angular.module.service:定義一個服務,可以註入其它內容,在服務中引用,本身也可以被引用。
(4),angular.module.factory:和service類似。
(5),angular.module.controller:定義controllers。
(6),angular.module.config:用來對已經存在的服務進行配置。
數據代碼(模擬數據):project-list.js
angular.module('project').value('projectListValue', [{ name: 'AngularJS', site: 'http://angularjs.org', description: 'HTML enhanced for web apps!' }, { name: 'Angular', site: 'http://angular.io', description: 'One framework. Mobile and desktop.' }, { name: 'jQuery', site: 'http://jquery.com/', description: 'Write less, do more.' }, { name: 'Backbone', site: 'http://backbonejs.org/', description: 'Models for your apps.' }, { name: 'SproutCore', site: 'http://sproutcore.com/', description: 'A Framework for Innovative web-apps.' }, { name: 'Polymer', site: 'https://www.polymer-project.org/', description: 'Reusable components for the modern web.' }, { name: 'Spine', site: 'http://spinejs.com/', description: 'Awesome MVC Apps.' }, { name: 'Cappucino', site: 'http://www.cappuccino-project.org/', description: 'Objective-J.' }, { name: 'Knockout', site: 'http://knockoutjs.com/', description: 'MVVM pattern.' }, { name: 'GWT', site: 'http://www.gwtproject.org/', description: 'JS in Java.' }, { name: 'Ember', site: 'http://emberjs.com/', description: 'Ambitious web apps.' }, { name: 'React', site: 'https://facebook.github.io/react/', description: 'A JavaScript library for building user interfaces.' } ])
四,創建組件
指令(Directives):指令是angular JS中一個獨特而又強大的特點,它讓你創建新的html標簽,只在你的應用範圍內有效。
可重用組件(Reusable Components):我們通過指令來創建可重用組件,組件讓你能夠隱藏複雜的dom結構,css和行為,它讓你只關註應用做什麼或者應用的外觀中的一個方面。將兩者分開來處理。
本地化(Localization):本地化是一個嚴謹或者說正式的APP中非常重要的一個方面。angular js的本地化感知過濾器和阻擋指令給你獨特的模塊,讓你的應用適用於所有區域。
先看一個示例代碼:
html:
<!DOCTYPE html> <!--ng-app激活這個頁面區域的APP 模塊,這個模塊包括BeerCounter controller,而且依賴於components module 它包含html擴展命令<tabs>和<pane>組件。--> <html ng-app="app"> <head> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" href="../backend/css/bootstrap.min.css" /> <link rel="stylesheet" href="../b