Angular簡單應用剖析

  這一篇咱們將一塊兒感覺學習一個小型的、活生生的應用,而不是繼續深刻分析哪些單個的特性。咱們將會一塊兒感覺一下,前面所討論過的全部片斷如何才能真正的組合在一塊兒,造成一個真實的、能夠運行的應用。javascript

GutHub是一款菜譜管理應用。咱們學習它有兩個目的,第一是用它來管理美味的菜譜,第二是用它來學習angularjs的方方面面:css

這款應用的特性以下:html

a)         兩列布局java

b)         在左側有一個導航欄angularjs

c)         容許你建立新菜譜bootstrap

d)         容許你瀏覽現有的菜譜列表數組

主視圖位於右側,主視圖將會根據具體的URL刷新,惋惜顯示菜譜列表、菜譜項目詳情、以及用來添加或編輯菜譜的表單。promise

 

 

模型、控制器和模版(視圖)之間的關係

         這三種東西是如何協做的,應用應該用什麼樣的視角來看待它們。服務器

         模型就是真理。你的整個應用都是由模型驅動的——視圖中全部展現的內容都是視圖、被存儲起來的內容是模型,幾乎全部的內容都是模型。因此請把「模型就是真理」這句話多讀幾遍。同時花一點時間思考一下模型,思考一下對象中的屬性應該是怎樣的內容、應該如何從服務器端獲取模型,以及如何保存它。經過視圖綁定技術,視圖會根據數據模型自動刷新,因此模型老是應用的焦點。架構

 

    控制器將會負責業務邏輯:應該如何獲取模型、能夠在模型上執行何種操做、視圖須要模型上的何種信息,如何如何轉換模型以獲取想要的信息。表單校驗任務、對服務器調用、使用正確的數據啓用視圖,以及與此相關的幾乎全部的事情都是控制器的職責。

   

    最有,模版表明模型的展示形式,以及用戶應該如何與應用進行交互。模版主要用來作一下事情:

n  展現模型

n  定義用戶與應用之間的交互方式

n  給應用提供樣式,而且判斷什麼時候以及怎樣顯示一些元素

n  過濾並格式化數據

 

視圖是模版和模型融合以後產生的東西。模版中不該該包含任何業務邏輯和行爲,只有控制器才能具有這些特性。可是你可能會問,DOM操做應該放到那裏呢?DOM操做並不會發生在控制器或模版中。它由angular指令負責(有時候也能夠經過服務進行操做,DOM操做放到放到服務中能夠避免重複代碼)。

   

      模型

         對於當前這款應用,咱們將會讓模型保持超級簡單。這裏的模型就是一些菜譜,它們將是真個應用的惟一的模型對象,其餘全部的東西都是構建在模型之上。

         每一天菜譜都具備一下特性:

  •   一個ID,這個ID將會被持久化到服務器
  •   一個名稱
  •   一個簡短的描述
  •   烹飪指南
  •   是不是特點菜
  •   配料數組,每一項都包含重量、單位及名稱

 

就這麼多,超級簡單,應用中全部的東西都圍繞這個簡單的模型而構建,下面是一天簡單的菜譜:

 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 }
View Code

 

 

咱們繼續來看,圍繞這個簡單的模型如何構建更加複雜的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 }]);
View Code

 

 

 

在以往的章節中已經接觸過了服務,這裏咱們再來深刻理解下。在上面的代碼中咱們實現了三個服務。其中有一個菜譜服務,它返回的東西叫作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請求。

 

其次,還有兩個服務,正兩個服務都是加載器:一個是單個菜單加載器,另個一是全部菜單加載器。當咱們鏈接到路由上去,就會用到這兩個加載器。它們的核心工做原理很是類似。這兩個服務的工做流程以下:

  1. 建立一個延遲對象(這些都是angularjs中的promise,用來對異步函數的鏈式調用)
  2. 向服務端發起一次調用
  3. 在服務端返回數據以後解析延遲對象
  4. 返回promise,angularjs中的路由將會機制將會使用這個對象

 

Promise是一個接口,他用來處理的對象具備這樣的特色:在將來的某一時刻(主要是異步調用)會從服務端返回或者被填充屬性。其核心是,promise是一個帶有then函數的對象.

使用promise機制的優勢以下:

  1. 能夠對函數鏈式調用,因此你不會陷入代碼縮進噩夢中。
  2. 在調用鏈的過程當中,能夠保證上一個函數調用完以後纔會調用下一個函數。
  3. 每個then()都帶有兩個參數(兩個都是函數),一個是成功以後的回調,一個是出錯以後的處理器。
  4. 若是調用鏈中出現了錯誤,錯誤將會冒泡傳遞到其他錯誤處理函數中。因此,最終來講,全部的錯誤均可以在任意一個回調函數中處理。

 

你可能會問,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 });
View Code

 

 

 

以上指令將會返回一個對象,這個對象只有一個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  
View Code

 

你可能注意到了,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>
View Code

 

 

在這個模版中有幾個比較有趣的元素須要注意,其中大部分已經聊過。其中包括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>
View Code  

 

這是一個平淡無奇模版,只有兩個地方很是的有趣,一個是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  
View Code  

 

菜譜表單模版: 

 

 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>
View Code

 

項目架構圖:

 

 

 

這個項目比較簡單、就不上效果圖了。你們能夠寫一個本身的demo來熟悉angular的各個模塊。

相關文章
相關標籤/搜索