2012-10-12 95 views
51

Tengo una aplicación angular que contiene un botón de guardar tomado de los ejemplos:AngularJS: la integración con la validación del lado del servidor

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

Esto funciona muy bien para la validación del lado del cliente, ya form.$invalid se convierte en falso como usuario soluciona los problemas, pero Tengo un campo de correo electrónico que se establece como no válido si otro usuario está registrado con el mismo correo electrónico.

Tan pronto como establezca el campo de mi correo electrónico no válido, no puedo enviar el formulario y el usuario no tiene forma de corregir ese error de validación. Así que ahora ya no puedo usar form.$invalid para deshabilitar mi botón de envío.

Tiene que haber una mejor manera

Respuesta

27

Lo necesitaba en algunos proyectos, así que creé una directiva. Finalmente se tomó un momento para ponerlo en GitHub para cualquiera que quiera una solución de instalación directa.

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

Características:

  • gota en solución para la validación Ajax de cualquier entrada de texto o la contraseña

  • Funciona con angulares construir en la validación y la cabina puede acceder en nomForm. inputName. $ error.ngRemoteValidate

  • aceleradores solicitudes de servidor (400 ms por defecto) y se puede configurar con ng-remote-throttle="550"

  • Permite método HTTP definición (POST defecto) con ng-remote-method="GET"

Ejemplo de uso para una forma de cambio de contraseña que requiere que el usuario introduzca su contraseña actual, así como la nueva contraseña .:

<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

¡Esto funciona genial! Estoy usando la versión 0.6.1. Veo que hay un problema abierto que no utiliza 'inputnameSetArgs'. Y una solución para agregar '$ parent' al alcance. Cambié esto manualmente y luego se llama a 'SetArgs' con parámetros adicionales enviados al servidor. – Guus

+0

Funciona perfectamente, gracias por compartir! –

73

Este es otro caso en el que una directiva personalizada es su amigo. Deberá crear una directiva e inyectar $ http o $ resource en ella para hacer una devolución de llamada al servidor mientras está validando.

Algunos pseudo código para la directiva personalizada:

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); 
     }) 
    } 
    } 
}); 

Y aquí es como que tendría que utilizar en el margen de beneficio:

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

EDIT: una pequeña explicación de lo que está pasando arriba.

  1. Cuando se actualiza el valor de la entrada, se actualiza la scope.userEmail $
  2. La directiva tiene un reloj de $ en $ scope.userEmail se creó en su función de enlace.
    • Cuando se activa el reloj $ se hace una llamada al servidor a través $ http ajax llamada, pasando el correo electrónico
    • El servidor se compruebe la dirección de correo electrónico y devolver una respuesta simple como '{isValid: true}
    • esa respuesta se usa para $ setValidity del control.
  3. Hay un en el marcado con ng-show establecido para mostrar solo cuando el estado de validez de correo electrónico único es falso.

... al usuario que quiere decir:

  1. Tipo del correo electrónico.
  2. pausa leve.
  3. El mensaje "El correo electrónico no es exclusivo" muestra "tiempo real" si el correo electrónico no es único.

EDIT2 EDIT2: Esto también le permite utilizar el formulario. $ Invalid para deshabilitar su botón de envío.

+1

pero esto no soluciona realmente mi problema. aún podrían enviar un correo electrónico no exclusivo y aún así debo validarlo en el lado del servidor, y en ese momento aún debo mostrar un error del cliente y no a través de esta directiva. – mkoryak

+0

pero estoy divagando, si pueden enviar un correo electrónico no válido, el envío devolverá un error y marcará el formulario no válido, en cuyo punto se verán obligados a usar esta directiva para marcar el campo de correo electrónico válido antes de que puedan enviar el formulario – mkoryak

+0

Esta directiva enviaría una solicitud de forma asíncrona al servidor, que verificaría el correo electrónico y diría si es único o no del lado del servidor ... si no aparece un mensaje de validación. –

16

He creado plunker con la solución que funciona perfecta para mí. Utiliza directivas personalizadas pero en forma completa y no en un solo campo.

http://plnkr.co/edit/HnF90JOYaz47r8zaH5JY

No recomendaría deshabilitar botón de enviar para la validación del servidor.

+3

¡Eso es extremadamente útil! Tuve que hacer un cambio: las versiones más nuevas no tienen angular.forEach, así que lo actualicé para usar $ .each: http://plnkr.co/edit/1UNljzvr70F6O6Tac1R8 – jphoward

+1

Tengo problemas para ejecutar esta actualización de plunker (jphoward) creado. ¿Qué versión de angular no es compatible con Each? Te veo incluido "1.1.1". –

5

Ok.En caso de que si alguien necesita versión de trabajo, es aquí:

de 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.) 

Esto me hizo pensar que no necesitamos: $scope.$apply(function(s) { de lo contrario, se quejan de $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> 

El controlador podría verse así:

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

Cambiaría esto para usar el angular [$ timeout] (https://docs.angularjs.org/api/ng/service/$timeout) en lugar de setTimeout –

+0

@ses: ¡Funciona, está bien! Solo quiero llamarlo onblur, o onchange. es posible? ¿Es esta una manera correcta? – VBMali

2

Gracias a las respuestas de esta página aprendieron sobre https://github.com/webadvanced/ng-remote-validate

Directivas de opción, que es un poco menos de lo que realmente no me gusta, ya que cada campo para escribir la directiva. El módulo es el mismo: una solución universal.

Pero en los módulos me faltaba algo - verifique en el campo varias reglas.
Luego acabo de modificar el módulo https://github.com/borodatych/ngRemoteValidate
Disculpas por el LÉAME ruso, eventualmente se alterará.
Me apresuro a compartir de repente tener a alguien con el mismo problema.
Sí, y nos hemos reunido aquí para esto ...

carga:

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

incluyen:

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> 

BackEnd [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); 
} 
Cuestiones relacionadas