Como adicionar validação personalizada a um formulário AngularJS?

Eu tenho um formulário com campos de input e configuração de validação, adicionando os atributos required e tal. Mas para alguns campos eu preciso fazer alguma validação extra. Como eu poderia “tocar” na validação que o FormController controla?

A validação personalizada pode ser algo como “se esses 3 campos estiverem preenchidos, esse campo será necessário e precisará ser formatado de uma maneira específica”.

Há um método em FormController.$setValidity mas isso não parece uma API pública, então eu prefiro não usá-lo. Criar uma diretiva personalizada e usar NgModelController parece outra opção, mas basicamente exigiria que eu criasse uma diretiva para cada regra de validação personalizada, o que eu não quero.

Na verdade, marcar um campo do controlador como inválido (ao mesmo tempo em que mantém o FormController em sincronia) pode ser o que preciso no cenário mais simples para concluir o trabalho, mas não sei como fazer isso.

Edit: adicionou informações sobre ngMessages (> = 1.3.X) abaixo.

Mensagens de validação de formulário padrão (1.0.X e acima)

Como esse é um dos principais resultados se você usar o Google “Angular Form Validation”, no momento, quero adicionar outra resposta a essa pergunta para qualquer um que chegar de lá.

Há um método em FormController. $ SetValidity, mas isso não parece uma API pública, então eu prefiro não usá-lo.

É “público”, não se preocupe. Use-o. É para isso. Se não fosse destinado a ser usado, os desenvolvedores de Angular teriam privatizado isso em um fechamento.

Para fazer uma validação personalizada, se você não quiser usar o Angular-UI como a outra resposta sugerida, basta rotacionar sua própria diretiva de validação.

 app.directive('blacklist', function (){ return { require: 'ngModel', link: function(scope, elem, attr, ngModel) { var blacklist = attr.blacklist.split(','); //For DOM -> model validation ngModel.$parsers.unshift(function(value) { var valid = blacklist.indexOf(value) === -1; ngModel.$setValidity('blacklist', valid); return valid ? value : undefined; }); //For model -> DOM validation ngModel.$formatters.unshift(function(value) { ngModel.$setValidity('blacklist', blacklist.indexOf(value) === -1); return value; }); } }; }); 

E aqui está um exemplo de uso:

 
The phrase "{{data.fruitName}}" is blacklisted required

Nota: em 1.2.X é provavelmente preferível replace ng-if por ng-show acima

Aqui está um link plunker obrigatório

Além disso, eu escrevi algumas inputs de blog sobre apenas este assunto que entra em um pouco mais de detalhe:

Validação de Formulário Angular

Diretivas de validação personalizada

Editar: usando ngMessages em 1.3.X

Agora você pode usar o módulo ngMessages em vez de ngShow para mostrar suas mensagens de erro. Ele vai realmente funcionar com qualquer coisa, não tem que ser uma mensagem de erro, mas aqui está o básico:

  1. Incluir
  2. Referência ngMessages na declaração do seu módulo:

     var app = angular.module('myApp', ['ngMessages']); 
  3. Adicione a marcação apropriada:

     
    required
    invalid email

Na marcação acima, ng-message="personForm.email.$error" basicamente especifica um contexto para as diretivas ng-message . Em seguida, ng-message="required" e ng-message="email" especificam propriedades nesse contexto a serem observadas. Mais importante ainda, eles também especificam um pedido para registrá-los . O primeiro que encontrar na lista que é “truthy” ganha, e mostrará essa mensagem e nenhuma das outras.

E um plunker para o exemplo ngMessages

O projeto do Angular-UI inclui uma diretiva ui-validate, que provavelmente irá ajudá-lo com isso. Isso permite que você especifique uma function para fazer a validação.

Dê uma olhada na página de demonstração: http://angular-ui.github.com/ , procure até o header Validar.

Na página de demonstração:

  This e-mail is black-listed! 

então no seu controlador:

 function ValidateCtrl($scope) { $scope.blackList = ['bad@domain.com','verybad@domain.com']; $scope.notBlackListed = function(value) { return $scope.blackList.indexOf(value) === -1; }; } 

Você pode usar o ng-required para o seu cenário de validação (“se estes 3 campos estiverem preenchidos, então este campo é obrigatório”:

 

Você pode usar o Angular-Validator .

Exemplo: usando uma function para validar um campo

  

Então, no seu controlador, você teria algo como

 $scope.myCustomValidationFunction = function(firstName){ if ( firstName === "John") { return true; } 

Você também pode fazer algo assim:

  

(onde campo1 campo2 e campo3 são variables ​​de escopo. Você também pode querer verificar se os campos não são iguais à string vazia)

Se o campo não passar no validator , o campo será marcado como inválido e o usuário não poderá enviar o formulário.

Para mais casos de uso e exemplos, consulte: https://github.com/turinggroup/angular-validator

Disclaimer: Eu sou o autor do Angular-Validator

Aqui está uma maneira legal de fazer validações de expressões curinga personalizadas em um formulário (de: Validação de formulário avançada com AngularJS e filtros ):

 
 app.directive('ensureExpression', ['$http', '$parse', function($http, $parse) { return { require: 'ngModel', link: function(scope, ele, attrs, ngModelController) { scope.$watch(attrs.ngModel, function(value) { var booleanResult = $parse(attrs.ensureExpression)(scope); ngModelController.$setValidity('expression', booleanResult); }); } }; }]); 

Demonstração do jsFiddle (suporta nomes de expressões e múltiplas expressões)

É similar a ui-validate , mas você não precisa de uma function de validação específica do escopo (isso funciona genericamente) e é claro que você não precisa de ui.utils dessa forma.

Recentemente, criei uma diretiva para permitir a invalidação baseada em expressão de inputs de formulário angular. Qualquer expressão angular válida pode ser usada e suporta chaves de validação personalizadas usando a notação de object. Testado com v1.3.8 angular

  .directive('invalidIf', [function () { return { require: 'ngModel', link: function (scope, elm, attrs, ctrl) { var argsObject = scope.$eval(attrs.invalidIf); if (!angular.isObject(argsObject)) { argsObject = { invalidIf: attrs.invalidIf }; } for (var validationKey in argsObject) { scope.$watch(argsObject[validationKey], function (newVal) { ctrl.$setValidity(validationKey, !newVal); }); } } }; }]); 

Você pode usá-lo assim:

  

@sinergético Eu acho @blesh suponha colocar a function validar como abaixo

 function validate(value) { var valid = blacklist.indexOf(value) === -1; ngModel.$setValidity('blacklist', valid); return valid ? value : undefined; } ngModel.$formatters.unshift(validate); ngModel.$parsers.unshift(validate); 

Atualizar:

Versão melhorada e simplificada da diretiva anterior (uma em vez de duas) com a mesma funcionalidade:

 .directive('myTestExpression', ['$parse', function ($parse) { return { restrict: 'A', require: 'ngModel', link: function (scope, element, attrs, ctrl) { var expr = attrs.myTestExpression; var watches = attrs.myTestExpressionWatch; ctrl.$validators.mytestexpression = function (modelValue, viewValue) { return expr == undefined || (angular.isString(expr) && expr.length < 1) || $parse(expr)(scope, { $model: modelValue, $view: viewValue }) === true; }; if (angular.isString(watches)) { angular.forEach(watches.split(",").filter(function (n) { return !!n; }), function (n) { scope.$watch(n, function () { ctrl.$validate(); }); }); } } }; }]) 

Exemplo de uso:

   

Resultado: expressões de teste mutuamente dependentes em que os validadores são executados na alteração do modelo de diretiva de outro e do modelo atual.

A expressão de teste tem uma variável de $model local que você deve usar para compará-la a outras variables.

Anteriormente:

Eu tentei melhorar o código @Plantface adicionando diretivas extras. Esta diretiva extra é muito útil se nossa expressão precisar ser executada quando mudanças forem feitas em mais de uma variável ngModel.

 .directive('ensureExpression', ['$parse', function($parse) { return { restrict: 'A', require: 'ngModel', controller: function () { }, scope: true, link: function (scope, element, attrs, ngModelCtrl) { scope.validate = function () { var booleanResult = $parse(attrs.ensureExpression)(scope); ngModelCtrl.$setValidity('expression', booleanResult); }; scope.$watch(attrs.ngModel, function(value) { scope.validate(); }); } }; }]) .directive('ensureWatch', ['$parse', function ($parse) { return { restrict: 'A', require: 'ensureExpression', link: function (scope, element, attrs, ctrl) { angular.forEach(attrs.ensureWatch.split(",").filter(function (n) { return !!n; }), function (n) { scope.$watch(n, function () { scope.validate(); }); }); } }; }]) 

Exemplo de como usá-lo para fazer campos validados cruzados:

    

ensure-expression é executada para validar o modelo quando ng-model ou qualquer variável de ensure-watch é alterada.

Em AngularJS, o melhor local para definir a validação personalizada é a diretiva Cutsom. O AngularJS fornece um módulo ngMessages.

ngMessages é uma diretiva projetada para mostrar e ocultar mensagens com base no estado de um object de chave / valor que ele atende. A própria diretiva complementa o relatório de mensagem de erro com o object de erro ngModel $ (que armazena um estado de chave / valor de erros de validação).

Para validação de formulário personalizado Deve-se usar ngMessages Modules com diretiva personalizada.Aqui eu tenho uma validação simples que irá verificar se o comprimento do número é menor que 6 exibir um erro na canvas

  
Too Short

Aqui está como criar diretiva de validação personalizada

 angular.module('myApp',['ngMessages']); angular.module('myApp',['ngMessages']).directive('customValidation',function(){ return{ restrict:'A', require: 'ngModel', link:function (scope, element, attr, ctrl) {// 4th argument contain model information function validationError(value) // you can use any function and parameter name { if (value.length > 6) // if model length is greater then 6 it is valide state { ctrl.$setValidity('invalidshrt',true); } else { ctrl.$setValidity('invalidshrt',false) //if less then 6 is invalide } return value; //return to display error } ctrl.$parsers.push(validationError); //parsers change how view values will be saved in the model } }; }); 

$setValidity é uma function embutida para definir o estado do modelo como válido / inválido

Eu estendi a resposta de @Ben Lesh com a capacidade de especificar se a validação é sensível a maiúsculas ou minúsculas (padrão)

usar:

  

código:

 angular.module('crm.directives', []). directive('blacklist', [ function () { return { restrict: 'A', require: 'ngModel', scope: { 'blacklist': '=', }, link: function ($scope, $elem, $attrs, modelCtrl) { var check = function (value) { if (!$attrs.casesensitive) { value = (value && value.toUpperCase) ? value.toUpperCase() : value; $scope.blacklist = _.map($scope.blacklist, function (item) { return (item.toUpperCase) ? item.toUpperCase() : item }) } return !_.isArray($scope.blacklist) || $scope.blacklist.indexOf(value) === -1; } //For DOM -> model validation modelCtrl.$parsers.unshift(function (value) { var valid = check(value); modelCtrl.$setValidity('blacklist', valid); return value; }); //For model -> DOM validation modelCtrl.$formatters.unshift(function (value) { modelCtrl.$setValidity('blacklist', check(value)); return value; }); } }; } ]); 

Validações personalizadas que chamam um servidor

Use a API $asyncValidators que lida com a validação assíncrona, como fazer uma solicitação $http para o backend. Funções adicionadas ao object devem retornar uma promise que deve ser resolvida quando válida ou rejeitada quando inválida. As validações assíncronas em andamento são armazenadas pela chave em ngModelController.$pending . Para obter mais informações, consulte o AngularJS Developer Guide – Forms (Custom Validation) .

 ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { var value = modelValue || viewValue; // Lookup user by username return $http.get('/api/users/' + value). then(function resolved() { //username exists, this means validation fails return $q.reject('exists'); }, function rejected() { //username does not exist, therefore this validation passes return true; }); }; 

Para mais informações, veja

  • API $asyncValidators

  • AngularJS Developer Guide – Formulários (validação personalizada) .

Alguns ótimos exemplos e libs apresentados neste tópico, mas eles não tinham exatamente o que eu estava procurando. Minha abordagem: validade angular – uma validação baseada em validação lib para validação assíncrona, com estilo Bootstrap opcional embutido.

Uma solução de validade angular para o caso de uso do OP pode ser algo como isto:

  

Aqui está um violino , se você quiser dar uma volta. A lib está disponível no GitHub , possui documentação detalhada e muitas demonstrações ao vivo.