2012-10-12 86 views
51

我有一個包含從實例中所保存按鈕的角度應用:AngularJS:與服務器端驗證集成

<button ng-click="save" ng-disabled="form.$invalid">SAVE</button> 

這個偉大的工程爲客戶端驗證,因爲form.$invalid爲用戶解決問題變爲假,但我有一個電子郵件字段,如果其他用戶使用相同的電子郵件註冊,則該字段將設置爲無效。

只要我將我的電子郵件字段設置爲無效,我就無法提交表單,並且用戶無法修復驗證錯誤。所以現在我不能再使用form.$invalid來禁用我的提交按鈕。

必須有更好的辦法

回答

27

我需要這個在一些項目中,所以我創建了一個指令。最後花了一些時間把它放在GitHub上,供任何想要一個插入式解決方案的人使用。

https://github.com/webadvanced/ng-remote-validate

特點:

  • 下降解決方案,用於驗證和駕駛室的任何文本或密碼輸入

  • 工程與Angulars構建Ajax驗證在窗體名稱進行訪問。 inputName。$ error.ngRemoteValidate

  • 節流服務器請求(默認400毫秒),並且可以與ng-remote-throttle="550"

  • 設置允許HTTP方法定義(默認POST)與ng-remote-method="GET"

實施例的使用用於改變密碼形式,要求用戶輸入他們的當前密碼以及新密碼:

<h3>Change password</h3> 
<form name="changePasswordForm"> 
    <label for="currentPassword">Current</label> 
    <input type="password" 
      name="currentPassword" 
      placeholder="Current password" 
      ng-model="password.current" 
      ng-remote-validate="/customer/validpassword" 
      required> 
    <span ng-show="changePasswordForm.currentPassword.$error.required && changePasswordForm.confirmPassword.$dirty"> 
     Required 
    </span> 
    <span ng-show="changePasswordForm.currentPassword.$error.ngRemoteValidate"> 
     Incorrect current password. Please enter your current account password. 
    </span> 

    <label for="newPassword">New</label> 
    <input type="password" 
      name="newPassword" 
      placeholder="New password" 
      ng-model="password.new" 
      required> 

    <label for="confirmPassword">Confirm</label> 
    <input ng-disabled="" 
      type="password" 
      name="confirmPassword" 
      placeholder="Confirm password" 
      ng-model="password.confirm" 
      ng-match="password.new" 
      required> 
    <span ng-show="changePasswordForm.confirmPassword.$error.match"> 
     New and confirm do not match 
    </span> 

    <div> 
     <button type="submit" 
       ng-disabled="changePasswordForm.$invalid" 
       ng-click="changePassword(password.new, changePasswordForm);reset();"> 
      Change password 
     </button> 
    </div> 
</form> 
+0

這很好用!我正在使用版本0.6.1我發現使用'inputnameSetArgs'沒有被調用是一個開放的問題。還有一個解決方案,將'$ parent'添加到範圍中。我手動改變了這個,然後'SetArgs'被調用,並向服務器發送額外的參數。 – Guus

+0

完美的作品,謝謝你的分享! –

73

這是自定義指令是你的朋友的另一種情況。您需要創建一個指令,並在其中注入$ http或$ resource,以便在您驗證時回撥至服務器。

自定義指令有些僞代碼:

app.directive('uniqueEmail', function($http) { 
    var toId; 
    return { 
    restrict: 'A', 
    require: 'ngModel', 
    link: function(scope, elem, attr, ctrl) { 
     //when the scope changes, check the email. 
     scope.$watch(attr.ngModel, function(value) { 
     // if there was a previous attempt, stop it. 
     if(toId) clearTimeout(toId); 

     // start a new attempt with a delay to keep it from 
     // getting too "chatty". 
     toId = setTimeout(function(){ 
      // call to some API that returns { isValid: true } or { isValid: false } 
      $http.get('/Is/My/EmailValid?email=' + value).success(function(data) { 

       //set the validity of the field 
       ctrl.$setValidity('uniqueEmail', data.isValid); 
      }); 
     }, 200); 
     }) 
    } 
    } 
}); 

下面是你如何使用它的標記:

<input type="email" ng-model="userEmail" name="userEmail" required unique-email/> 
<span ng-show="myFormName.userEmail.$error.uniqueEmail">Email is not unique.</span> 

編輯:什麼上面發生的一個小的解釋。

  1. 當您更新的輸入值,它更新$ scope.userEmail
  2. 該指令對$ scope.userEmail一個$手錶它建立在它的連接功能。
    • 當$手錶被觸發,使得對服務器的呼叫通過$ HTTP Ajax調用,通過電子郵件
    • 服務器將檢查的電子郵件地址,並返回像「一個簡單的迴應{isValid方法:真正}
    • 該響應用於控制的$ setValidity。
  3. 在ng-show設置的標記中只有當uniqueEmail有效性狀態爲false時才顯示。

...到,這意味着用戶:

  1. 類型的電子郵件。
  2. 稍微暫停。
  3. 如果電子郵件不是唯一的,則「電子郵件不唯一」消息顯示「實時」。

EDIT2:這也允許你使用form。$ invalid來禁用你的提交按鈕。

+1

但這並不能真正解決我的問題。他們仍然可能會提交一個非唯一的電子郵件,我仍然必須在服務器端驗證它,在這一點上,我仍然必須顯示客戶端錯誤,而不是通過此指令? – mkoryak

+0

但我離題,如果他們能夠提交一個無效的電子郵件,提交將返回一個錯誤和標記形式無效,在這一點上,他們將被迫使用此指令標記電子郵件字段有效之前,他們可以提交表格 – mkoryak

+0

這個指令會向服務器異步發送一個請求,服務器會檢查電子郵件並說服務器端是否唯一或者不在服務器端......如果這不是驗證消息,則顯示。 –

16

我已經創建了適合我的完美解決方案。它使用自定義指令,但在整個表單上,而不是在單個字段上。

http://plnkr.co/edit/HnF90JOYaz47r8zaH5JY

我不會建議禁止用於服務器驗證的提交按鈕。

+3

這非常有幫助!我必須做出一個改變:最新版本沒有angular.forEach,所以我更新它使用$ .each:http://plnkr.co/edit/1UNljzvr70F6O6Tac1R8 – jphoward

+1

我有麻煩來運行此更新你( jphoward)創建。什麼版本的角度不支持forEach?我看到你包括「1.1.1」。 –

5

好的。在情況下,如果有人需要工作版本,它是在這裏:

從DOC:

$apply() is used to enter Angular execution context from JavaScript 

(Keep in mind that in most places (controllers, services) 
$apply has already been called for you by the directive which is handling the event.) 

這使我想到,我們並不需要:$scope.$apply(function(s) {否則會抱怨$digest

app.directive('uniqueName', function($http) { 
    var toId; 
    return { 
     require: 'ngModel', 
     link: function(scope, elem, attr, ctrl) { 
      //when the scope changes, check the name. 
      scope.$watch(attr.ngModel, function(value) { 
       // if there was a previous attempt, stop it. 
       if(toId) clearTimeout(toId); 

       // start a new attempt with a delay to keep it from 
       // getting too "chatty". 
       toId = setTimeout(function(){ 
        // call to some API that returns { isValid: true } or { isValid: false } 
        $http.get('/rest/isUerExist/' + value).success(function(data) { 

         //set the validity of the field 
         if (data == "true") { 
          ctrl.$setValidity('uniqueName', false); 
         } else if (data == "false") { 
          ctrl.$setValidity('uniqueName', true); 
         } 
        }).error(function(data, status, headers, config) { 
         console.log("something wrong") 
        }); 
       }, 200); 
      }) 
     } 
    } 
}); 

HTML:

<div ng-controller="UniqueFormController"> 

     <form name="uniqueNameForm" novalidate ng-submit="submitForm()"> 

      <label name="name"></label> 
      <input type="text" ng-model="name" name="name" unique-name> <!-- 'unique-name' because of the name-convention --> 

      <span ng-show="uniqueNameForm.name.$error.uniqueName">Name is not unique.</span> 

      <input type="submit"> 
     </form> 
    </div> 

控制器可能看起來像這樣:

app.controller("UniqueFormController", function($scope) { 
    $scope.name = "Bob" 
}) 
+0

我會改變這個使用角度[$超時](https://docs.angularjs.org/api/ng/service/$timeout),而不是setTimeout –

+0

@ses:它的作品,很好!只是想在onblur或onchange上打電話。這可能嗎?這是一個正確的方式嗎? – VBMali

2

由於從該頁面的答案瞭解https://github.com/webadvanced/ng-remote-validate

選項指令,比我不太喜歡的選項指令少,因爲每個字段都寫入指令。 模塊是相同的 - 一個通用的解決方案。

但是在模塊中,我錯過了一些東西 - 檢查字段中的幾條規則。
然後我只是修改模塊https://github.com/borodatych/ngRemoteValidate
俄羅斯自述文件的道歉最終會改變。
我急於分享突然有人有同樣的問題。
是的,我們爲此聚集在這裏......

負載:

<script type="text/javascript" src="../your/path/remoteValidate.js"></script> 

包括:

var app = angular.module('myApp', [ 'remoteValidate' ]); 

HTML

<input type="text" name="login" 
ng-model="user.login" 
remote-validate="('/ajax/validation/login', ['not_empty',['min_length',2],['max_length',32],'domain','unique'])" 
required 
/> 
<br/> 
<div class="form-input-valid" ng-show="form.login.$pristine || (form.login.$dirty && rv.login.$valid)"> 
    From 2 to 16 characters (numbers, letters and hyphens) 
</div> 
<span class="form-input-valid error" ng-show="form.login.$error.remoteValidate"> 
    <span ng:bind="form.login.$message"></span> 
</span> 

後端[Kohana的]

public function action_validation(){ 

    $field = $this->request->param('field'); 
    $value = Arr::get($_POST,'value'); 
    $rules = Arr::get($_POST,'rules',[]); 

    $aValid[$field] = $value; 
    $validation = Validation::factory($aValid); 
    foreach($rules AS $rule){ 
     if(in_array($rule,['unique'])){ 
      /// Clients - Users Models 
      $validation = $validation->rule($field,$rule,[':field',':value','Clients']); 
     } 
     elseif(is_array($rule)){ /// min_length, max_length 
      $validation = $validation->rule($field,$rule[0],[':value',$rule[1]]); 
     } 
     else{ 
      $validation = $validation->rule($field,$rule); 
     } 
    } 

    $c = false; 
    try{ 
     $c = $validation->check(); 
    } 
    catch(Exception $e){ 
     $err = $e->getMessage(); 
     Response::jEcho($err); 
    } 

    if($c){ 
     $response = [ 
      'isValid' => TRUE, 
      'message' => 'GOOD' 
     ]; 
    } 
    else{ 
     $e = $validation->errors('validation'); 
     $response = [ 
      'isValid' => FALSE, 
      'message' => $e[$field] 
     ]; 
    } 
    Response::jEcho($response); 
}