天天看點

《AngularJS深度剖析與最佳實踐》一1.4 實作第一個頁面:注冊

本節書摘來自華章出版社《angularjs深度剖析與最佳實踐》一書中的第1章,第1.4節,作者 雪狼 破狼 彭洪偉,更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視

接下來,我們開始實作第一個疊代的第一個功能:10.注冊。

我們把能夠通過url獨立通路的一項功能簡稱為“一個路由”,這裡為注冊功能配置設定一個叫作/reader/create的路由。

之是以不使用/register的形式,是希望在各個url之間保持統一,這也是我們在整個項目中将貫穿的一個約定。

如同後端開發一樣,我們将reader稱為controller,create稱作action,中間還可以有一個id,是以,典型的url是這樣的:/$controller/:id?/$action,其中的id字段是可以省略的,取決于具體的action。

這樣,我們在url和檔案所在的路徑之間就可以建立一個簡單的映射關系:拿到一個url,如/reader/1/edit,其中reader是$controller,edit是$action,于是我們知道它的代碼位于app/controllers/reader目錄下,其模闆為edit.html,控制器為edit.js,樣式為edit.scss。

這有什麼好處呢?比如測試人員報告說一個頁面存在bug,其url是/book/1/preview,我們一看bug報告,判斷出其錯誤位于控制器中,于是,我們直接開始修改app/controllers/book/preview.js。

如果使用的是intellij/webstorm,那麼我們隻要按下cmd-shift-n(navigate|file...,mac下)組合鍵,輸入preview.js,然後回車,就可以直接開始編輯了。其他ide也有類似的功能。

不要小看這一點點約定,它可以節省很多不必要的時間浪費,特别是在多人協作開發時,你的代碼可能會被很多人修改,如果連這個都需要溝通或者閱讀代碼,那麼浪費的時間和精力也是很可觀的。

配置設定完url,我們還需要把這個url和控制器、模闆對應起來。雖然我們有了一個約定,但是程式并不知道,我們還需要找個地方聲明一下,這個地方就是app/configs/router.js。

範例工程會給它生成一個代碼骨架,為了友善加注釋,我對一些語句進行了額外換行,實際代碼中是不需要這麼多換行的:

定義完路由,我們仍然不能直接通過url通路它,如果通路則會在浏覽器控制台中發現一個錯誤資訊:angular.js:10126 error: [ng:areq] argument 'readercreatectrl' is not a function, got undefined(angular.js:10126 錯誤:[ng:areq]參數'readercreatectrl'不是函數,而是未定義。),其原因是沒有找到名為readercreatectrl的控制器。

接下來,我們就對它所引用的模闆和控制器進行實作。

首先我們要建立一個app/controllers/reader/create.js檔案,和一個app/controllers/reader/create.html檔案,我們來看一個空白的create.js檔案:

接下來,我們就要開始實作它們了。

如果已經有ux(使用者體驗設計師)或ba(業務分析師)給出的原型圖,那麼建議從設計model的資料結構開始,這樣有助于更深入的了解angular開發中最顯著的特點:模型驅動。如果是從零開始,也可以先設計html。我們這個項目的開發是單人項目,是以我們先直接設計html。到本節的最後,我再來講解根據原型圖做模型驅動開發的過程。

我們要設計一個html,但不用設計一個“漂亮的”html。注意,在一個項目組中,不同的角色是需要分工的,要把ux擅長的工作留給ux;即使是單人項目,也需要把不同類型的工作分開完成。

在注冊頁,我們需要一個表單,它具有如下業務意義上的字段:郵箱、昵稱、密碼、确認密碼;還需要一些技術和法律意義上的字段:圖形驗證碼(captcha)、網站服務協定、“同意服務協定”複選框。

由于我們的業務并不需要手機号、年齡之類的字段,那麼我們就不要收集它。這種“最小資訊”原則,可以幫助你在受到安全攻擊的時候把損失控制在最小。同時,把需要填寫的内容控制在最小範圍内,也有利于提升使用者體驗。

我們的第一個html頁面如下:

注意,用來在input和label之間建立關聯的id字段都是用下劃線開頭的,這并不是随意為之,而是要把id留給寫“端到端測試”的人員。我們把這些不能不用的id全用下劃線開頭,有助于防止潛在的沖突。

現在,我們切到浏覽器中,會看到一個很難看的表單,如圖1-1所示。

《AngularJS深度剖析與最佳實踐》一1.4 實作第一個頁面:注冊

雖然簡陋,但已經足夠表現我們的html骨架了。

接下來,我們需要把它修到可正常互動的級别。

首先,我們需要給每個輸入型字段(input/textarea/select等)綁定一個model變量。僅以郵箱字段為例,我們把它修改為:

這裡的ng-model就是angular中一系列“魔法”的關鍵。它是一個angular指令,其作用是把所在的input元素和ng-model=""中的表達式建立雙向綁定,這種雙向綁定意味着,當表達式的值發生變化時,input的value會跟着變化,反過來,當input中的value 由于使用者操作而發生變化時,綁定表達式的值也會相應跟着變化。而且這兩者的資料格式并不需要保持一緻,ng-model指令提供了一系列機制在兩者之間進行轉換。

ng-model并不限于用在input/textarea/select元素中,事實上,它幾乎可以用在任何元素中。但隻有這幾個元素可以直接使用ng-model,用于其他元素時需要自己寫自定義指令來實作雙向綁定。這是因為angular自帶了對input/textarea/select的重寫指令,重定義了它們的行為,使其可以支援ng-model。在後面的章節,我們會看到如何在自定義指令中支援ng-model。

這裡還涉及另一項約定和最佳實踐:把目前表單的所有字段綁定到一個叫作vm.form的對象中,這樣我們就可以很友善的把表單資料作為一個整體進行處理,比如送出或重置。

除了“确認密碼”和“同意協定”之外的其他的字段可以以此類推,不再摘引源碼。

“确認密碼”字段之是以特殊,是因為它并不需要最終送出給服務端,我們隻是為了防止使用者輸入錯誤,靠前端來校驗就已經足夠了。我們設想一下攻擊場景,發現對它隻做前端校驗并不會構成安全漏洞。是以,我們不需要把它綁定到vm.form對象的屬性中去,用個獨立的vm.retypedpassword變量就可以了。

而“同意協定”字段也同樣可以依靠純前端驗證。在法律上,我們隻要盡到了提醒和詢問的義務即可。如果使用者通過非正規手段繞過前端直接通路服務端,不能作為未曾同意協定的借口。

注意,前面我們隻是修改了html就已經完成了雙向綁定,我們并沒有在控制器中定義vm.form.email變量,甚至連vm.form對象都沒有定義。這是因為在angular中做了容錯處理,發現一個變量沒有定義時,它會自動幫我們定義一下,而不會觸發錯誤,這個特性在寫模闆時非常有用。

“圖形驗證碼”的字段也比較特殊,我們把它留到下一節去處理。現在,我們的界面就已經到了最初的可互動級别。

接下來我們有兩個分支可以走:套用bootstrap類進行初步美化(ux分支),或者開始實作表單送出代碼(程式員分支),兩者可以同時進行。

為了盡快看到“可行走骨架”,我們選擇程式員的分支:實作表單送出代碼。要在收集了表單資料的基礎上進一步實作表單送出,我們就要借助另一個指令了,這個指令叫作ng-submit。

直覺上,我們可能希望在送出按鈕上綁定一個事件來完成表單送出,但更好的方式是在form上綁定一個ng-submit事件。這是因為觸發表單送出并不是隻有點選“送出”按鈕這一種方式,使用者還可以在input中敲Enter鍵來直接送出表單,在大多數場景下,這是更為友好的方式。但更重要的是,這樣的html,其表意性更強一些。

這個步驟對代碼的影響很小,在html中,我們隻要把

改為`javascript

-submit="vm.submit(vm.form)">即可,在javascript中增加幾句指令即可:

vm.submit = function(form) {

};

module.exports = function (config) {

angular.module('com.ngnice.app').factory('reader', function readerfactory ($resource) {

});

angular.module('com.ngnice.app').controller('readercreatectrl', function readercreatectrl($resource) {

angular.module('com.ngnice.app').controller('readercreatectrl', function readercreatectrl(reader) {

reader.save(form,

);

{

}

$$validitystate: validitystate

$dirty: false

$error: object

$formatters: array[2]

$invalid: true

$isempty: function (value) {...

$modelvalue: undefined

$name: "email"

$parsers: array[2]

$pristine: true

$render: function () {...

$setpristine: function () {...

$setvalidity: function (validationerrorkey, isvalid) {...

$setviewvalue: function (value) {...

$valid: false

$viewchangelisteners: array[0]

$viewvalue: undefined

angular.module('com.ngnice.app').directive(

// 指令名稱,它會按照約定轉換成減号分隔的辨別符後才能在模闆中使用:<code>bf-field-error</code>,這裡的bf是book-forum的縮寫,用這個字首來防止和其他指令沖突。

'bffielderror', function bffielderror($compile) {

var hint = $compile('

{{name}}')(subscope);

{{name | error}}')(subscope);

angular.module('com.ngnice.app').filter('error', function () {

1)app/constants/errors.js:

angular.module('com.ngnice.app').constant('errors', {

2)app/filters/error.js:

angular.module('com.ngnice.app').filter('error', function (errors) {

angular.module('com.ngnice.app').directive('bfassertsameas', function bfassertsameas() {

...

angular.module('com.ngnice.app').directive('bfcaptcha', function bfcaptcha() {

繼續閱讀