走進AngularJs(一)angular基本概念的認識與實戰


一、前言

  前端技術的發展是如此之快,各種優秀技術、優秀框架的出現簡直讓人目不暇接,作為一名業界新秀,緊跟時代潮流,學習掌握新知識自然是不敢怠慢。當聽到AngularJs這個名字並知道是google在維護它時,便一直在關注,看到其在國外已經十分火熱,可是國內的使用情況卻有不小的差距,參考文獻/網絡文章也很匱乏。朝思暮想良久,決定深入學習angular,並寫系列博客,一方面作為自己學習路程上的記錄,另一方面也給有興趣的同學一些參考。

  首先我自己是一名學習者,會以學習者的角度來整理我的行文思路,故該系列博客也不能叫做教程,只能是些探索,有理解或是技術上的錯誤還請大家指出。其次我特別喜歡編寫小例子來把一件事情說明白,故在文中會盡可能多的用示例加代碼講解,我相信這會是一鍾比較好的方式。最后,我深知在現有條件下對於angular的學習會困難重重,不過我更相信堅持的力量,所以謹以此文作為日后學習的動力,讓我們一起來走進angular的世界吧~

二、AngularJs是什么

  這個定義一定要定准了,AngularJs(后面就簡稱ng了)是一個用於設計動態web應用的結構框架。首先,它是一個框架,不是類庫,是像backbone一樣提供一整套方案用於設計web應用。它不僅僅是一個javascript框架,因為它的核心其實是對HTML標簽的增強,有圖有真相,請看官網描述:

 

  何為HTML標簽增強?其實就是使你能夠用標簽完成一部分頁面邏輯,具體方式就是通過自定義標簽、自定義屬性等,這些HTML原生沒有的標簽/屬性在ng中有一個名字:指令(directive)。后面會詳細介紹。那么,什么又是動態web應用呢?與傳統web系統相區別,web應用能為用戶提供豐富的操作,能夠隨用戶操作不斷更新視圖而不進行url跳轉。ng官方也聲明它更適用於開發CRUD應用,即數據操作比較多的應用,而非是游戲或圖像處理類應用。

  為了實現這些,ng引入了一些非常棒的特性,包括模板機制、數據綁定、模塊、指令、依賴注入、路由。通過數據與模板的綁定,能夠讓我們擺脫繁瑣的DOM操作,而將注意力集中在業務邏輯上。這些我將在以后的學習中一一研究。

  另外一個疑問,ng是MVC框架嗎?還是MVVM框架?官網有提到ng的設計采用了MVC的基本思想,而又不完全是MVC,因為在書寫代碼時我們確實是在用ng-controller這個指令(起碼從名字上看,是MVC吧),但這個controller處理的業務基本上都是與view進行交互,這么看來又很接近MVVM。讓我們把目光移到官網那個非醒目的title上:“AngularJS — Superheroic JavaScript MVW Framework”。

  好吧,MVW。W—whatever。隨便是MV什么好了,所以也有人寫為了MV*。其實糾結這個也真沒必要,等今后對整個框架熟悉了,其中結構自然了然於心。

三、開始運行angular

  有了一個大概的朦朧的了解就夠了,我相信很多概念在使用的過程中會慢慢清晰。我迫不及待的想要讓angular運行起來了。動手~

  首先從官網http://angularjs.org/下載angular.js,引入你的頁面中,然后我們使用最簡單的手工啟動方式,直接調用bootstrap方法。所有的代碼如下:

<!DOCTYPE html>
<html >
<head>
<meta charset="utf-8" />  
<title>運行ng</title>
<script type="text/javascript" src="../angular.js"></script>
</head>
<body>
<div>
    1+1={{1+1}}
</div>   
<script>
angular.bootstrap(document,[]);
</script>
</body>
</html>

  只有一行代碼。調用bootstrap方法傳入作用域和初始化的模塊數組(此處為空)。是不是很簡單。你很快會看到一處比較特別的,就是這里:

<div>
    1+1={{1+1}}
</div>

  如果你使用過其他模板庫,應該對這種寫法不陌生了,{{}}雙大括號,這是ng的模板中用於書寫表達式的標記,ng成功運行起來后,{{}}內的表達式會生效,即頁面會顯示如下:

 

  為了不讓你把ng看的這么簡單,我必須告訴你一般是不這么啟動的,來看稍微修改以后的代碼:

<!DOCTYPE html>
<html ng-app="MyApp">
<head>
<meta charset="utf-8" />  
<title>運行ng</title>
<script type="text/javascript" src="../angular.js"></script>
</head>
<body>
<div>
    1+1={{1+1}}
</div>
   
<script type="text/javascript" charset="utf-8">
var app = angular.module('MyApp', [], function(){console.log('started')});
</script>
</body>
</html>

  在<html>標簽上多了一個屬性ng-app=”MyApp”,它的作用就是用來指定ng的作用域是在<html>標簽以內部分。在js中,我們調用angular對象的module方法來聲明一個模塊,模塊的名字和ng-app的值對應。關於如何聲明、使用模塊我們在后面會講。現在我們只要明白用這種方式可以優雅的讓ng運行起來就可以了。

四、模板與數據的綁定

  首先需要明確一下模板的概念。在我還不知道有模板這個東西的時候,曾經用js拼接出很長的HTML字符串,然后append到頁面中,這種方式想想真是又土又笨。后來又看到可以把HTML代碼包裹在一個<script>標簽中當作模板,然后按需要取來使用。在ng中,模板十分簡單,它就是我們頁面上的HTML代碼,不需要附加任何額外的東西。在模板中可以使用各種指令來增強它的功能,這些指令可以讓你把模板和數據巧妙的綁定起來。

  綁定這個東西可是ng中的大功臣了。在我們使用jQuery的時候,代碼中會大量充斥類似這樣的語句:var v = $(‘#id’).val();$(‘#id’).html(str);即頻繁的DOM操作(讀取和寫入),其實我們的最終目的並不是要操作DOM,而是要實現業務邏輯。ng的綁定將讓你擺脫DOM操作,只要模板與數據通過聲明進行了綁定,兩者將隨時保持同步,最新的數據會實時顯示在頁面中,頁面中用戶修改的數據也會實時被記錄在數據模型中。

  我構思了一個小例子,本篇文章的示例將圍繞這個小例子來進行。我猜你已經厭倦了登錄模塊或者是購物車示例,我們來點新穎的。我已化身為一名教師,我要用一個web應用來為學生出一份在線試題。首先從一道題開始吧~

<!DOCTYPE html>
<html ng-app="MyApp">
<head>
<meta charset="utf-8" />  
<title>模板數據綁定</title>
<script type="text/javascript" src="../angular.js"></script>
</head>
<body>
<div ng-controller="testC">
    <h1>{{newtitle}}</h1>
    題目:<input type="text" ng-model="name" /><br />
    分數:<input type="text" ng-model="fraction" /><br />
    <hr>
    <h1>{{previewtitle}}</h1>
    <b>{{name}}</b>({{fraction}}分)
</div>
<script type="text/javascript" charset="utf-8">
var app = angular.module('MyApp', [], function(){console.log('started')});
var testC = function($scope){
    $scope.newtitle = '新建試題';
    $scope.previewtitle = '預覽試題';
    $scope.name = $scope.fraction = '';
}
</script>
</body>
</html>

  頁面上有分別表示題目和分數的兩個輸入框,下面有一個實時預覽區域,用來展示題目的最終輸出效果。ng-controller=”textC”用來聲明一個需要和數據進行綁定的模板區域,它的作用域就是這個div之內的東西,並起名為textC,js代碼中定義了一個名為textC的函數與它對應,我們將在這個函數內完成綁定。函數傳入一個參數$scope,表示這個作用范圍。我們分別為作用范圍內的newtitle、previewtitle、name、fraction四個變量賦值。此時<h1>標簽內的{{}}便能得到相應的值了。

  通過{{}}只能完成數據向模板的單向綁定。要想進行雙向綁定,我們需要用到ng-modle這個指令,我們使用它分別為題目和分數進行了雙向綁定,這樣當輸入框內的值發生變化時,函數中的變量也會跟隨變化,它的變化會實時反饋在下方的預覽區域中,因為預覽區域中也有一個name和fraction的綁定。

  試試在下面的輸入框中進行編輯吧~

  我們並未進行任何DOM操作,框架自動完成了DOM的取值和賦值。在后面我將繼續完善這個例子,並引入更多的新概念。

五、模板中的一些控制方式

  我們在使用其他模板庫時,一般都會有模板的循環輸出、分支輸出、邏輯判斷等類似的控制。ng模板中都可以進行哪些控制呢?來一塊探索之。

    1.循環輸出

  繼續上面的例子。試題光有題目和分數還不夠,我想要出一道選擇題,可以自己添加若干選項並編輯選項內容,代碼該怎么寫呢?先上代碼:

  HTML部分:

<div ng-controller="testC">
    <h1>{{question.newtitle}}</h1>
    題目:<input type="text" ng-model="question.name" /><br />
    分數:<input type="text" number ng-model="question.fraction" /><br />
    選項:<button ng-click="addOption()">增加選項</button><br />
    <ul>
        <li ng-repeat="o in question.options">
            <b>{{$index+1}}.</b>
            <input type="text" ng-model="o.content" value="o.content" />
            <a href="javascript:void(0);" ng-click="delOption($index)">刪除</a>
        </li>
    </ul>
    <hr>
    <div >
        <h1>{{question.previewtitle}}</h1>
        <b>{{question.name}}</b>({{question.fraction}}分)
        <ul>
            <li ng-repeat="o in question.options">
                <b>{{$index+1}}.</b>
                <input type="radio" name="optcheck" />
                {{o.content}}
            </li>
        </ul>
    </div>
</div>

  js部分:

var app = angular.module('MyApp', [], function(){console.log('started')});
var questionModel = {
    newtitle : '新建試題',
    previewtitle : '預覽試題',
    name : '',
    fraction : '',
    options : []
};
app.controller('testC',function($scope){
    $scope.question = questionModel;
    $scope.addOption = function(){
        var o = {content:''};
        $scope.question.options.push(o);
    };
    $scope.delOption = function(index){
        $scope.question.options.splice(index,1);
    };
});

  請注意我的js代碼有了一點變化,在數據綁定時沒有直接寫成字符串,而是將所有的數據寫成了一個questionModel對象,這只是一個普遍的js對象,這樣能夠使我們的數據看起來像是“model”的樣子,畢竟我們是MV*嘛。那么在模板中,我們使用name的地方也改成question.name,其他的值也同理。另外在controller部分,我沒有用var textC = function(){}這樣的寫法,而是改成了app.controller(‘textC’,function(){}),這樣明確指定了這個controller屬於MyApp這個模塊,我們的MV*代碼看起來更專業、更一體了。

  在HTML部分我分別在新建區域和預覽區域添加了一個列表,用來放置各選項。並添加了一個“增加選項”按鈕。在<li>標簽上使用了ng-repeat指令來進行循環輸出,<li>標簽將會根據$scopt中的options數組長度被復制多份。在循環范圍內,可以使用$index獲得當前循環的索引,可以認為這是一個公共變量,直接使用。同時,循環輸出的每個<input>元素也使用ng-model與選項的內容進行了雙向綁定。另外還輸出一個刪除鏈接,點擊的時候調用$scope中的delOption方法。

  這樣綁定好之后,模版中的列表與數據模型中的options數組便保持了同步,我們在頁面上點擊增加選項,數組中便會增加一個元素,數組中的每個元素也會實時反饋在模板中。所以在js代碼中,我們只須操作options數組就夠了,壓根不需要在頁面上append或removed節點。

  你可以在下面點擊增加選項和刪除試試~

  2.單個節點的控制

  在上面的例子中,你是不是發現了,我在處理按鈕的點擊時,使用了叫做ng-click的指令,為什么不直接用onclick呢?是因為ng根據自己的需要進行了封裝。我們把addOption這個函數定義在了controller范圍之內,用我們常規的onclick已經無法訪問到。換言之,我們頁面上的作用域,ng已經幫我們都規划好了,我們只需按照它提供的方式來使用就夠了。

  通過onclick我們可以聯想到,HTML節點還有好多其他屬性,如class、style、href、checked、disabled等等,ng對這些都一一進行了封裝,更厲害的是,除此之外ng還額外提供了許多更加詳細的控制節點的指令。這些指令我以后會詳細研究,在這里,我們先拿個其中一個應用到我們的例子中,看看效果先。

  我馬上變回老師身份,嘩~

  我新建試題的時候,不要局限於單選題,我想要在單選題和多選題之間能夠切換,同時下方的預覽區域內,選擇框也進行單選框和多選框的切換。上代碼:

  HTML部分:

<div ng-controller="testC">
    <h1>{{question.newtitle}}</h1>
    題目:<input type="text" ng-model="question.name" /><br />
    分數:<input type="text" ng-model="question.fraction" /><br />
    類型:<select ng-model="question.type"><option value="1" selected>單選</option><option value="2">多選</option></select><br />
    選項:<button ng-click="addOption()">增加選項</button><br />
    <ul>
        <li ng-repeat="o in question.options">
            <b>{{$index+1}}.</b>
            <input type="text" ng-model="o.content" value="o.content" />
            <a href="javascript:void(0);" ng-click="delOption($index)">刪除</a>
        </li>
    </ul>
    <hr>
    <div preview-panel>
        <h1>{{question.previewtitle}}</h1>
        <b>{{question.name}}</b>({{question.fraction}}分)
        <ul>
            <li ng-repeat="o in question.options">
                <b>{{$index+1}}.</b>
                <input type="radio" name="optcheck" ng-show="question.type==1" />
                <input type="checkbox" ng-show="question.type==2" />
                {{o.content}}
            </li>
        </ul>
    </div>
</div>

  Js代碼中,我只是在questionModel中新增了一項type:1,表示題的類型,1為單選,2為多選。並給默認值為1.

var questionModel = {
    newtitle : '新建試題',
    previewtitle : '預覽試題',
    name : '',
    fraction : '',
    type : '1',
    options : []
};

  在HTML中,我新增了一個下拉框,並與question.type建立雙向綁定。需要關注的是下面的預覽區域。我又添加了一個checkbox控件來為多選題提供選擇框。顯然,radio與checkbox不能同時存在,所以我用ng-show這個指令來控制它們的顯隱,ng-show接收boolean類型的值以及計算結果為boolean類型的表達式。請注意,ng-show以及其他所有指令的值不是簡單的字符串(盡管看上去是那樣),而是字符串表達式,擁有計算能力,我現在的理解是它將來會在框架的某個地方放進eval()或是類似的函數執行。{{}}里的內容也是一樣。

  所以在這里我給radio的ng-show賦值為question.type==1,checkbox的ng-show賦值為question.type==2。當試題是單選題時,radio的ng-show便能得到值true,從而顯示出來。在下面看一下效果:

  其他的節點控制指令也可以類似這樣使用,思想就是根據你的業務邏輯,賦予它們一定的表達式。其他的控制還有事件綁定、表單控件等等,篇幅的限制在這里就不講了,以后專門開一篇介紹。

  3.過濾器(filter)

  所謂過濾器是指對輸出的內容進行格式化,如格式化為美元、日期等。框架自己提供一些過濾器,如排序、字符串內容篩選。我們也可以自定義過濾器。

  過濾器在{{}}中使用,表達式后用|隔開使用。拿日期過濾器舉例,方式如下:

$scope.nowTime = new Date().valueOf();
{{nowTime | date : 'yyyy-MM-dd HH:mm:ss'}}

  便會輸出格式化的日期。是不是很方便呢。

  接下來實戰一下,嘩~

  接上一步的例子,我想要在預覽區域中的題目前面顯示題型,像[單選題]這樣。我們的questionModel中,type的值是1和2,所以我們要做的就是通過過濾器,把1和2顯示為單選題和多選題。Go~

  在js中定義一個名為typeFilter的過濾器:

app.filter('typeFilter',function(){
    var f = function(input){
        return input == '1' ? '單選題' : '多選題';
    }
    return f;
});

  filter函數如何使用以及執行細節不是本篇的討論內容,所以現在只要明白這樣可以定義一個過濾器就可以了。結構姑且認為是固定寫法,代碼不難看懂。

定義后這個filter后我們便可以在模板中使用了:

<b>[{{question.type | typeFilter}}]{{question.name}}</b>({{question.fraction}}分)

  在題目的前面顯示題型,並使用typeFilter,效果如下:

 

  單調的“1”已經華麗轉身變為了“單選題”。這只是一個簡單的過濾器。你可以發揮想象力編寫更強大的過濾器。

六、指令(directive)

  前面已經提到很多次指令了,現在來正式介紹一下它。指令是ng為HTML補充的語法擴展,用於增強HTML的表現力。像我們之前使用的ng-controller、ng-model等都屬於指令。你也可以自定義指令。ng內部包含了一個強大的DOM解析引擎,所以這些新的標簽或是標簽屬性可以像使用原生HTML那樣很好的工作。

  聽起來很牛的樣子,那我們來試試自己定義一個指令吧。注意,我要變形了~

  我是教師,在新建試題輸入分數的時候應該只能輸入數字才對,輸入其他內容是不合法的,而且我希望這個分數是1~10之間的數字。能否只在輸入框上加一個屬性就完成這個驗證呢?就像使用HTML5新增的required一樣。

  我們定義一個叫做fractionNum的指令如下:

app.directive('fractionNum',function(){
    return {
        link : function(scope, elements, attrs, controller){
            elements[0].onkeyup = function(){
                if(isNaN(this.value) || this.value<1 || this.value>10){
                    this.style.borderColor = 'red';
                }
                else{
                    this.style.borderColor = '';
                }
            };
        }
    };
});

  哇,代碼好多層級呀,不要慌張,穩住陣腳!其實最后就是返回了帶有link字段的對象,link的值是一個函數,用來定義指令的行為。從傳入的參數中可以獲取到當前元素,我們便可以拿當前元素開刀了。我在此處監聽當前元素的keyup事件,獲取元素的值,如果不是1~10之間的數字,則把輸入框的邊框顏色變為紅色。這下這個指令就可以工作了。

  至於傳入的四個參數到底都有什么玄機,我暫時還未研究,也不是本篇的重點,本篇只是做一個概覽,先把ng拉出來溜溜的意思。現在姑且可以認為,一個指令的固定寫法大概就是這個結構。

  定義好的指令就可以在模板中使用了,使用方法如下:

分數:<input type="text" ng-model="question.fraction" fraction-num /><br />

  把它加在了分數輸入框上,此處要特別小心一個寫法,我定義的時候名字是fractionNum,用在模板中需要寫成fraction-num,就是因為名字中含有大寫字母的原因,感覺上跟使用css屬性名稱有點像。如果定義的時候沒有大寫字母,就不必擔心這一點了。

  看效果,現在你可以去下面蹂躪那個分數輸入框去了~

七、依賴注入

  通過依賴注入,ng想要推崇一種聲明式的開發方式,即當我們需要使用某一模塊或服務時,不需要關心此模塊內部如何實現,只需聲明一下就可以使用了。在多處使用只需進行多次聲明,大大提高可復用性。

  比如我們的controller,在定義的時候用到一個$scope參數。

app.controller('testC',function($scope){});

  如果我們在此處還需操作其他的東西,比如與瀏覽器地址欄進行交互。我們只需再多添一個參數$location進去:

app.controller('testC',function($scope,$location){});

  這樣便可以通過$location來與地址欄進行交互了,我們僅僅是聲明了一下,所需的其他代碼,框架已經幫我們注入了。我們很明顯的感覺到了這個函數已經不是常規意義上的javascript函數了,在常規的函數中,把形參換一個名字照樣可以運行,但在此處若是把$scope換成別的名字,程序便不能運行了。因為這是已經定義好的服務名稱。

  這便是依賴注入機制。順理成章的推斷,我們可以自己定義模塊和服務,然后在需要的地方進行聲明,由框架來替我們注入。

  對,我的小例子呢?我現在要想點需求來把依賴注入試驗一下。我覺得試題全是文字太單調了,我希望題目中能含有圖片/音視頻,或者選項中可以含有圖片/音視頻。並且,我要更靈活一點,我要為試題提供若干套模板來選擇,選擇不同的模板可以新建不同樣式的題,比如模板一為純文字試題、模板二為題目中帶圖片/音視頻的試題、模板三為選項中帶圖片/音視頻的試題,等等。注意此處所說的模板跟ng的模板不是一個概念,是我自己試題的模板,不要混淆。這些模板應該是與試題相分離的,以后可以為其他題型例如填空題啊簡答題啊同樣使用。所以試題模板這個東西就需要做成服務了。

  不知道我表達清楚了沒有,確實有點繞。來看下我們如何定義一個服務:

app.factory('tpls',function(){
    return ['tpl1','tpl2','tpl3','tpl4'];
});

  看上去相當簡單,是因為我在這里僅僅是直接返回一個數組。在實際應用中,這里應該是需要向服務器發起一個請求,來獲取到這些模板們。服務的定義方式有好幾種,包括使用provider方法、使用factory方法,使用service方法。它們之間的區別暫且不關心。我們現在只要能創建一個服務出來就可以了。我使用了factory方法。一個需要注意的地方是,框架提供的服務名字都是由$開頭的,所以我們自己定義的最好不要用$開頭,防止發生命名沖突。

  定義好一個服務后,我們就可以在控制器中聲明使用了,如下:

app.controller('testC',function($scope,tpls){
    $scope.question = questionModel;
    $scope.nowTime = new Date().valueOf();
    $scope.templates = tpls; //賦值到$scope中
    $scope.addOption = function(){
        var o = {content:''};
        $scope.question.options.push(o);
    };
    $scope.delOption = function(index){
        $scope.question.options.splice(index,1);
    };
});

  此時,若在模板中書寫如下代碼,我們便可以獲取到服務tpls所提供的數據了:

模板:<a href="javascript:void(0);" ng-repeat="t in templates">{{t}}&nbsp;&nbsp;</a><br />

  隨着知識點的一點點增加,我的這個小例子也越來越豐滿了,來看看完整版吧:

  查看完整代碼請移步到runjs:http://runjs.cn/code/95wlwsfh

八、總結一下

    乎~可以松一口氣了。文章寫到這里終於接近尾聲了,不知我上面的陳述能否被大家理解。ng所包含的內容還是挺多的,以上的每個概念都可以再拆出幾篇文章來講解。所以我在這里只能是每一個都點到為止,不揪細節。這篇文章的思想也就是“先了解概念,例子能跑起來就行了”。在以后的文章中會隨着我學習的深入進行探討。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM