這一篇咱們將一塊兒感覺學習一個小型的、活生生的應用,而不是繼續深刻分析哪些單個的特性。咱們將會一塊兒感覺一下,前面所討論過的全部片斷如何才能真正的組合在一塊兒,造成一個真實的、能夠運行的應用。javascript
GutHub是一款菜譜管理應用。咱們學習它有兩個目的,第一是用它來管理美味的菜譜,第二是用它來學習angularjs的方方面面:css
這款應用的特性以下:html
a) 兩列布局java
b) 在左側有一個導航欄angularjs
c) 容許你建立新菜譜bootstrap
d) 容許你瀏覽現有的菜譜列表數組
主視圖位於右側,主視圖將會根據具體的URL刷新,惋惜顯示菜譜列表、菜譜項目詳情、以及用來添加或編輯菜譜的表單。promise
這三種東西是如何協做的,應用應該用什麼樣的視角來看待它們。服務器
模型就是真理。你的整個應用都是由模型驅動的——視圖中全部展現的內容都是視圖、被存儲起來的內容是模型,幾乎全部的內容都是模型。因此請把「模型就是真理」這句話多讀幾遍。同時花一點時間思考一下模型,思考一下對象中的屬性應該是怎樣的內容、應該如何從服務器端獲取模型,以及如何保存它。經過視圖綁定技術,視圖會根據數據模型自動刷新,因此模型老是應用的焦點。架構
控制器將會負責業務邏輯:應該如何獲取模型、能夠在模型上執行何種操做、視圖須要模型上的何種信息,如何如何轉換模型以獲取想要的信息。表單校驗任務、對服務器調用、使用正確的數據啓用視圖,以及與此相關的幾乎全部的事情都是控制器的職責。
最有,模版表明模型的展示形式,以及用戶應該如何與應用進行交互。模版主要用來作一下事情:
n 展現模型
n 定義用戶與應用之間的交互方式
n 給應用提供樣式,而且判斷什麼時候以及怎樣顯示一些元素
n 過濾並格式化數據
視圖是模版和模型融合以後產生的東西。模版中不該該包含任何業務邏輯和行爲,只有控制器才能具有這些特性。可是你可能會問,DOM操做應該放到那裏呢?DOM操做並不會發生在控制器或模版中。它由angular指令負責(有時候也能夠經過服務進行操做,DOM操做放到放到服務中能夠避免重複代碼)。
對於當前這款應用,咱們將會讓模型保持超級簡單。這裏的模型就是一些菜譜,它們將是真個應用的惟一的模型對象,其餘全部的東西都是構建在模型之上。
每一天菜譜都具備一下特性:
就這麼多,超級簡單,應用中全部的東西都圍繞這個簡單的模型而構建,下面是一天簡單的菜譜:
1 { 2 "id":"1", 3 "title":"熱薑汁藕片", 4 "description":"藕片切得越薄越容易入味。", 5 "ingredients":[ 6 { 7 "amount":"450", 8 "amountUnits":"g", 9 "ingredientName":"姜" 10 }, 11 { 12 "amount":"450", 13 "amountUnits":"g", 14 "ingredientName":"藕" 15 }, 16 { 17 "amount":"0", 18 "amountUnits":"0", 19 "ingredientName":"香油" 20 }, 21 { 22 "amount":"3", 23 "amountUnits":"g", 24 "ingredientName":"食鹽" 25 }, 26 { 27 "amount":"50", 28 "amountUnits":"mml", 29 "ingredientName":"白醋" 30 } 31 ], 32 "instructions":"一、準備食材 \n 二、姜用工具磨成薑蓉 \n 三、姜用工具磨成薑蓉 \n 四、藕片入開水汆一下,大概3分鐘斷生,撈出 \n 五、鍋裏倒入白醋 \n 六、燒至沸騰後加入薑蓉 \n 七、30秒後加入香油和鹽,將熱薑汁淋入藕片上,醃製20分鐘左右便可食用" 33 34 }
咱們繼續來看,圍繞這個簡單的模型如何構建更加複雜的UI功能。
咱們要完成這個應用須要幾個指令和控制器代碼,而後再來看所須要的控制器。
文件位置menuSolution\app\services\services.js
1 var services=angular.module('guthub.services',['ngResource']); 2 services.factory('Recipe',['$resource', function ($resource) { 3 return $resource('/recipes/:id',{id:'@id'}); 4 }]); 5 6 services.factory('MultiRecipeLoader',['Recipe','$q', function (Recipe, $q) { 7 return function () { 8 var delay=$q.defer(); 9 Recipe.query(function (recipes) { 10 delay.resolve(recipes); 11 }, function () { 12 delay.reject('沒法取出食譜'); 13 }); 14 return delay.promise; 15 }; 16 }]); 17 18 services.factory('RecipeLoader',['Recipe','$route','$q', function (Recipe,$route,$q) { 19 return function () { 20 var delay=$q.defer(); 21 Recipe.get( 22 {id:$route.current.params.recipeId}, 23 function (recipe) { 24 delay.resolve(); 25 }, 26 function () { 27 delay.reject('沒法取出食譜'+ $route.current.params.recipeId); 28 } 29 ); 30 31 return delay.promise; 32 }; 33 }]);
在以往的章節中已經接觸過了服務,這裏咱們再來深刻理解下。在上面的代碼中咱們實現了三個服務。其中有一個菜譜服務,它返回的東西叫作Angular Resource。Resource將會封裝底層的$http服務,因此你的代碼只要負責處理對象就能夠了。只要一行代碼——return $resource(固然須要依賴services.guthub模塊),咱們就能夠把菜譜做爲參數傳給任何控制器了,而後菜譜對象就會被注入到控制器中。這樣依賴,每一個菜譜都具有了下面這些內置的方法:
Recipe.get();
Recipe.save();
Recipe.delete();
Recipe.query();
Recipe.remove();
注:若是你打算使用Recipe.delete();,而且但願在ie中使用它,你必須這樣調用它Recipe[delete](),這是由於在IE中delete是一個關鍵字。
假設咱們如今有一個菜譜對象,必要的信息都已經放在就這個對象裏面了,包括Id。而後經過下面這些代碼咱們就能夠把它保存起來:
var recipe=new Recipe(obj);//假設id=13
recipe.$save();
以上代碼會向/recipe/13路徑發起一次POST請求。
其次,還有兩個服務,正兩個服務都是加載器:一個是單個菜單加載器,另個一是全部菜單加載器。當咱們鏈接到路由上去,就會用到這兩個加載器。它們的核心工做原理很是類似。這兩個服務的工做流程以下:
Promise是一個接口,他用來處理的對象具備這樣的特色:在將來的某一時刻(主要是異步調用)會從服務端返回或者被填充屬性。其核心是,promise是一個帶有then函數的對象.
使用promise機制的優勢以下:
你可能會問,resolve(解決)方法和reject(拒絕)方法又是什麼呢?在angular中延遲調用是實現promise的一種方式。調用resolve方法會填充promise(也就是調用success函數),而reject方法將會調用promise的錯誤處理函數。
如今咱們來看看應用中的指令。在目前這款應用中咱們會用到以下兩條指令。
Butterbar
當路由發生變化同時頁面還在加載時,這一指令將會顯示和隱藏信息的操做。指令將會被嵌入到路由的變化機制中,而後根據頁面的狀態自動隱藏或顯示其標籤中的內容。
Focus
Focus指令要你過來確保特定的輸入項(或元素)可否得到焦點。
文件位置 menuSolution\app\directives\directives.js
1 var directives=angular.module('guthub.directives',[]); 2 directives.directive('butterbar',['$rootScope', function ($rootScope) { 3 return { 4 link: function (scope,element,attrs) { 5 element.addClass('hide'); 6 $rootScope.$on('$routeChangeStart', function () { 7 element.removeClass('hide'); 8 }); 9 $rootScope.$on('$routeChangeSuccess', function () { 10 element.addClass('hide'); 11 }); 12 } 13 } 14 }]); 15 16 directives.directive('focus', function () { 17 return { 18 link: function (scope, element, sttrs) { 19 element[0].focus(); 20 } 21 }; 22 });
以上指令將會返回一個對象,這個對象只有一個link屬性。
一、 指令的處理過程分爲兩個步驟。在編譯過程當中,找到綁定在DOM元素上的指令,而後進行處理。全部的DOM操做都發生在編譯階段,在這一階段結束以後,會產生一個內聯函數。
二、 連接階段中,第一步所產生的DOM模版會變連接到做用域上,會根據須要添加的監控器或者監聽器,從而在做用域和元素之間進行動態綁定。這樣一來,與做用域相關的全部內容都是在連接階段進行的。
能夠像下面這樣使用butterbar指令:
<div butterbar> my loading text……</div>
在一開始的時候只是簡單把它隱藏起來,而後在做用域上添加兩個監聽器。每當路由器發生變化時,它就會顯示內部元素。每當路由成功完成變化以後,它又會把butterbar隱藏起來。
另外還有一個有趣的東西是,那就是如何把$tootScope注入到指令中。全部指令都會被直接鏈接到angularjs的依賴注入系統中。
第二個focus指令更加簡單。它實在調用當前元素上的focus()方法而已。你能夠在任何元素上添加focus屬性來調用它。
<input type=」text」 focus/>
當頁面加載完成以後,文本框會自動獲取焦點。
文件位置 menuSolution\app\controller\controller.js
寫完指令和服務以後,終於改寫控制器了,這裏咱們須要5個控制器。全部這些控制器都位於同一個文件夾中
1 var app=angular.module('guthub',['guthub.services','guthub.directives']); 2 //第一個控制器:List控制器,他的任務是顯示系統中全部菜譜 3 //請關注List控制器重要的一件事:在構造器中它不會到i服務器中獲取菜譜。相反它會處理一個已經獲取到的菜譜列表 4 app.controller('ListController',['$scope','recipes', function ($scope, recipes) { 5 $scope.recipes=recipes; 6 }]); 7 8 //其餘控制器與List控制器很是類似 9 //edit函數只是把URL地址改爲編輯的地址,而後angular就會去作剩餘的工做 10 app.controller('ViewController',['$scope','$location','recipe', function ($scope,$location,recipe) { 11 $scope.recipe=recipe; 12 $scope.edit= function () { 13 $location.path('/edit/'+recipe.id); 14 } 15 }]); 16 17 18 19 app.controller('EditController',['$scope','$location','recipe', function ($scope,$location,recipe) { 20 $scope.recipe=recipe; 21 $scope.save= function (recipe) { 22 $scope.recipe.save(function () { 23 $location.path('/view/'+recipe.id); 24 }); 25 }; 26 $scope.remove= function () { 27 delete $scope.recipe; 28 $location.path('/'); 29 }; 30 }]); 31 32 app.controller('NewController',['$scope','$location','Recipe', function ($scope,$location,Recipe) { 33 $scope.recipe=new Recipe( 34 { 35 ingredients:[{}] 36 } 37 ); 38 39 $scope.save= function () { 40 $scope.recipe.$save(function (recipe) { 41 $locale.path('/view/'+recipe.id); 42 }) 43 } 44 }]); 45 46 47 app.controller('IngredientsController',['$scope', function ($scope) { 48 $scope.AddIngredient= function () { 49 var ingredients=$scope.recipe.ingredients; 50 ingredients[ingredients.length]={}; 51 }; 52 $scope.removeIngredient= function (index) { 53 $scope.recipe.ingredients.splice(index,1); 54 } 55 }]); 56 57 58 59 60 //建立路由 61 app.config([ 62 '$routeProvider', 63 function ($routeProvider) { 64 $routeProvider.when('/', { 65 controller: 'ListController', 66 resolve: { 67 recipes: function (MultiRecipeLoader) { 68 return MultiRecipeLoader(); 69 } 70 }, 71 templateUrl: '/views/list.html' 72 }).when('/edit/:recipeId', { 73 controller: 'EditController', 74 resolve: { 75 recipe: function (RecipeLoader) { 76 return RecipeLoader(); 77 } 78 }, 79 templateUrl: '/views/recipeForm.html' 80 }).when('/view/:recipeId', { 81 controller: 'ViewController', 82 resolve: { 83 recipe: function (RecipeLoader) { 84 return RecipeLoader(); 85 } 86 }, 87 templateUrl: '/views/viewRecipe.html' 88 }).when('/new', { 89 controller: 'NewController', 90 templateUrl: '/views/recipeForm.html' 91 }).otherwise({redirectTo: '/'}); 92 } 93 ]); 94 95
你可能注意到了,edit和New這兩個控制器的路由都指向了相同的模版URL——-/views/recipeForm.html,是怎麼回事呢?由於咱們會根據關聯控制器的不一樣,在菜譜模版中顯示不一樣的元素。
作完這些以後,咱們來看看模版。看看這些控制器是如何把它們關聯起來的,以及如何管理顯示給最終用戶的內容。
咱們會從最外層的主模版入手,也是就index.html。他就是咱們單頁應用的根。其餘模版都會加載到這個模版的內部。
模版位置menuSolution\app\views\
主要的模版:
1 <!DOCTYPE html> 2 <html ng-app="guthub"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>GutHub</title> 6 <script src="static/js/angular.js" type="text/javascript"></script> 7 <script src="static/js/angular-resource.js" type="text/javascript"></script> 8 <script src="static/js/angular-route.min.js" type="text/javascript"></script> 9 <script src="app/directives/directives.js" type="text/javascript"></script> 10 <script src="app/services/services.js" type="text/javascript"></script> 11 <script src="app/controller/controller.js" type="text/javascript"></script> 12 <link rel="stylesheet" href="static/css/bootstrap.css"/> 13 <link rel="stylesheet" href="static/css/guthub.css"/> 14 </head> 15 <body> 16 <div butterbar>Loading......</div> 17 <div class="container"> 18 <div class="row"> 19 <header> 20 <h1>GutHub</h1> 21 </header> 22 <div class="span2"> 23 <div id="focus"><a href="/#/new">New Recipe</a><br/></div> 24 <div><a href="/#/">Recipe List</a></div> 25 </div> 26 <div class="span2"> 27 <div ng-view></div> 28 </div> 29 </div> 30 31 </div> 32 </body> 33 </html>
在這個模版中有幾個比較有趣的元素須要注意,其中大部分已經聊過。其中包括ng-app、ng-view、butterbar 明顯還缺乏ng-controller。
如今咱們來看每一個控制器上的模版:
菜譜列表模版:
1 <h3>Recipe List</h3> 2 <ul class="recips"> 3 <li ng-repeat="recipe in recipes"> 4 <div> 5 <a ng-href="/#/view/{{recipe.id}}">{{recipe.title}}</a> 6 </div> 7 </li> 8 </ul>
這是一個平淡無奇模版,只有兩個地方很是的有趣,一個是ng-repeat標籤使用的很是標準。它會從做用域中獲取菜單列表,而後遍歷它們;另外一個是使用ng-href代替了href,這純粹是爲了在angular加載過程當中產生錯誤連接,ng-href不管在什麼時候能夠保證都不會把存在缺陷的鏈接展現給用戶。這個模版完了,你可能會問,我怎麼沒看到控制器呢?並且咱們也沒有定義主控制器(Main controller)。這正式路由映射派上用場的地方。咱們以前說的路由會跳轉到列表模版,它上面綁定了List Controller,如過引用了變量之類的東西那麼變量就位於List Controller 做用於內。
第二個模版:
1 <h2>{{recipe.title}}</h2> 2 <div>{{recipe.description}}</div> 3 <ul class="list-unstyled"> 4 <li ng-repeat="ingredient in recipe.ingredients"> 5 <span>{{ingredient.amount}}</span> 6 <span>{{ingredient.amountUnits}}</span> 7 <span>{{ingredient.ingredientName}}</span> 8 </li> 9 </ul> 10 11 <h3>Intructions</h3> 12 <div>{{recipe.instructions}}</div> 13 <form ng-submit="edit()" class="form-horizontal"> 14 <div class="form-actions"> 15 <button class="btn btn-primary">修改</button> 16 </div> 17 </form> 18 19
菜譜表單模版:
1 <h2>Edit Recipe</h2> 2 <form name="recipeForm" ng-submit="save()" class="form-horizontal"> 3 <div class="control-group"> 4 <label class="control-label" for="title">標題:</label> 5 6 <div class="controls"> 7 <input type="text" ng-model="recipe.title" id="title" class="input-xlarge" focus/> 8 </div> 9 </div> 10 11 <div class="control-group"> 12 <label class="control-label" for="description">描述:</label> 13 14 <div class="controls"> 15 <input type="text" ng-model="recipe.description" id="description" class="input-xlarge" focus/> 16 </div> 17 </div> 18 19 20 <div class="control-group"> 21 <label class="control-label" for="ingredients">原材料:</label> 22 <ul class="controls" ng-controller="IngredientsController"> 23 <li ng-repeat="i in recipe.ingredients"> 24 <input type="text" ng-model="i.amount" class=""/> 25 <input type="text" ng-model="i.amountUnits" class=""/> 26 <input type="text" ng-model="i.ingredientName" class=""/> 27 <button class="btn btn-primary" ng-click="removeIngredient($index)"> 28 <i class="glyphicon-minus-sign"></i> 29 刪除 30 </button> 31 </li> 32 <button type="button" class="btn btn-primary" ng-click="AddIngredient()"> 33 <i class="glyphicon-plus-sign"></i> 添加 34 </button> 35 </ul> 36 <input type="text" ng-model="recipe.ingredients" id="ingredients" class="input-xlarge" focus/> 37 38 </div> 39 40 41 <div class="control-group"> 42 <label class="control-label" for="instructions">作法:</label> 43 44 <div class="controls"> 45 <input type="text" ng-model="recipe.instructions" id="instructions" class="input-xxlarge" focus/> 46 </div> 47 </div> 48 49 <div class="form-actions"> 50 <button class="btn btn-primary" ng-click="save()">保存</button> 51 <button class="btn btn-primary" ng-show="!recipe.id" ng-click="remove()">刪除</button> 52 </div> 53 </form>
項目架構圖:
這個項目比較簡單、就不上效果圖了。你們能夠寫一個本身的demo來熟悉angular的各個模塊。