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

278

Eu tenho um formulário com campos de entrada e configuração de validação, adicionando os requiredatributos e tal. Mas, para alguns campos, preciso fazer uma validação extra. Como eu "utilizaria" a validação queFormController controla?

A validação personalizada pode ser algo como "se esses 3 campos forem preenchidos, esse campo será obrigatório e precisará ser formatado de uma maneira específica".

Existe um método, FormController.$setValiditymas que não se parece com uma API pública, por isso prefiro não usá-lo. Criando uma Diretiva Customizada e UtilizandoNgModelController parece com outra opção, mas basicamente exigiria que eu criasse uma diretiva para cada regra de validação personalizada, que eu não quero.

Na verdade, marcar um campo do controlador como inválido (mantendo FormControllerem sincronia) pode ser o que eu preciso no cenário mais simples para concluir o trabalho, mas não sei como fazer isso.

botteaap
fonte
4
Há um bom artigo sobre codificação de monstros para lidar com validações personalizadas em JS angular. Verifique este para fora
Anshu
Não é exatamente o que estou procurando, pois requer diretivas personalizadas, mas aceitarei sua resposta, pois, de qualquer maneira, é um bom artigo.
botteaap
Gostaria de saber a mesma coisa, eu adoraria algum controle no nível do FormController. Por exemplo, quero que certas diretivas personalizadas sinalizem a instância do FormController como algo parecido formName.$warning.
Adam Waselnuk
2
Eu acredito que isso $$precede as APIs não públicas, por $serem públicas. Veja stackoverflow.com/questions/19338493/…
Daniel F

Respostas:

370

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

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

Como esse é um dos principais resultados se você pesquisar no Google "Validação de formulário angular", no momento, quero adicionar outra resposta a qualquer um que vier de lá.

Existe um método no FormController. $ SetValidity, mas que não se parece com uma API pública, por isso prefiro não usá-lo.

É "público", não se preocupe. Use-o. É para isso que serve. Se não fosse para ser usado, os desenvolvedores Angular o privatizariam em um fechamento.

Para fazer a validação personalizada, se você não quiser usar a Angular-UI como a outra resposta sugerida, basta rolar 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:

<form name="myForm" ng-submit="doSomething()">
   <input type="text" name="fruitName" ng-model="data.fruitName" blacklist="coconuts,bananas,pears" required/>
   <span ng-show="myForm.fruitName.$error.blacklist">
      The phrase "{{data.fruitName}}" is blacklisted</span>
   <span ng-show="myForm.fruitName.$error.required">required</span>
   <button type="submit" ng-disabled="myForm.$invalid">Submit</button>
</form>

Nota: em 1.2.x é provavelmente preferível substituir ng-ifa ng-showanterior

Aqui está um link de afunilador obrigatório

Além disso, escrevi algumas entradas de blog sobre esse assunto, que entram em detalhes um pouco mais:

Validação de formulário angular

Diretivas de validação personalizadas

Edit: usando ngMessages em 1.3.X

Agora você pode usar o módulo ngMessages em vez do ngShow para mostrar suas mensagens de erro. Na verdade, ele funcionará com qualquer coisa, não precisa ser uma mensagem de erro, mas aqui está o básico:

  1. Incluir <script src="angular-messages.js"></script>
  2. Referência ngMessagesna sua declaração de módulo:

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

    <form name="personForm">
      <input type="email" name="email" ng-model="person.email" required/>
    
      <div ng-messages="personForm.email.$error">
        <div ng-message="required">required</div>
        <div ng-message="email">invalid email</div>
      </div>
    </form>

Na marcação acima, ng-message="personForm.email.$error"basicamente especifica um contexto para as ng-messagediretivas filho. Em seguida, ng-message="required"e ng-message="email"especificar as propriedades nesse contexto para relógio. Mais importante, eles também especificam uma ordem para verificá-los . O primeiro que encontrar na lista "verdadeira" vence e mostra essa mensagem e nenhuma das outras.

E um plunker para o exemplo ngMessages

Ben Lesh
fonte
6
Se você retornar valor na função que passa para $ parsers.unshift, valores errados também serão salvos no modelo - seria melhor retornar indefinido, acredito (quando o valor não for válido).
precisa
5
+1 @georgiosd ... 100% correto. Observando o que o Angular faz, eles estão retornando indefinidos. Provavelmente não é grande coisa retornar o valor, já que (espero) modelos de formulários inválidos não são enviados ... mas é melhor prevenir do que remediar, suponho.
Ben Lesh
2
Coisas boas! Se você Googled seu caminho aqui à procura de um bom writeup na validação personalizada no Angular, confira o que @blesh escreveu
maaachine
Você verificou a validação avançada de formulário com AngularJS e filtros ? Ele resolve a validação do filtro genericamente.
Benny Bottema
1
Eu acho que você pode ter feito isso return value ? valid : undefinedacima.
GChorn 02/09/2015
92

O projeto da Angular-UI inclui uma diretiva de validação da interface do usuário, que provavelmente o ajudará com isso. Vamos especificar uma função a ser chamada para fazer a validação.

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

Na página de demonstração:

<input ng-model="email" ui-validate='{blacklist : notBlackListed}'>
<span ng-show='form.email.$error.blacklist'>This e-mail is black-listed!</span>

então no seu controlador:

function ValidateCtrl($scope) {
  $scope.blackList = ['[email protected]','[email protected]'];
  $scope.notBlackListed = function(value) {
    return $scope.blackList.indexOf(value) === -1;
  };
}
Pete BD
fonte
Como é estranho que isso não funciona para mim usando Angular 1.4
Nick
46

Você pode usar ng-required em seu cenário de validação ("se esses 3 campos forem preenchidos, esse campo será obrigatório":

<div ng-app>
    <input type="text" ng-model="field1" placeholder="Field1">
    <input type="text" ng-model="field2" placeholder="Field2">
    <input type="text" ng-model="field3" placeholder="Field3">
    <input type="text" ng-model="dependentField" placeholder="Custom validation"
        ng-required="field1 && field2 && field3">
</div>
Mario G.
fonte
2
Isso funcionou para mim. Para validações simples que depende de outros valores Campos, este é o caminho a percorrer, em vez de escrita regras validações complexas
VimalKumar
28

Você pode usar o Angular-Validator .

Exemplo: usando uma função para validar um campo

<input  type = "text"
    name = "firstName"
    ng-model = "person.firstName"
    validator = "myCustomValidationFunction(form.firstName)">

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:

<input  type = "text"
        name = "firstName"
        ng-model = "person.firstName"
        validator = "'!(field1 && field2 && field3)'"
        invalid-message = "'This field is required'">

(onde campo1 campo2 e campo3 são variáveis ​​de escopo. Você também pode verificar se os campos não são iguais à sequência vazia)

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

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

Disclaimer: Eu sou o autor do Angular-Validator

user3920706
fonte
13

Recentemente, criei uma diretiva para permitir a invalidação baseada em expressão de entradas de forma angular. Qualquer expressão angular válida pode ser usada e suporta chaves de validação personalizadas usando a notação de objeto. 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:

<input ng-model="foo" invalid-if="{fooIsGreaterThanBar: 'foo > bar',
                                   fooEqualsSomeFuncResult: 'foo == someFuncResult()'}/>

Ou apenas passando uma expressão (será fornecida a validationKey padrão de "invalidIf")

<input ng-model="foo" invalid-if="foo > bar"/>
Alex Schwartz
fonte
13

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

<form novalidate="">  
   <input type="text" id="name" name="name" ng-model="newPerson.name"
      ensure-expression="(persons | filter:{name: newPerson.name}:true).length !== 1">
   <!-- or in your case:-->
   <input type="text" id="fruitName" name="fruitName" ng-model="data.fruitName"
      ensure-expression="(blacklist | filter:{fruitName: data.fruitName}:true).length !== 1">
</form>
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 jsFiddle (suporta nomeação de expressões e várias expressões)

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

Benny Bottema
fonte
Obrigado. Muito legal. É especialmente útil aplicar regras de validação para formulários dinâmicos. No entanto, ele ainda define o valor do modelo, mesmo que seja inválido. De qualquer forma, para impedir que defina o modelValue se for inválido?
YuMei
5

Atualizar:

Versão aprimorada 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:

<input ng-model="price1" 
       my-test-expression="$model > 0" 
       my-test-expression-watch="price2,someOtherWatchedPrice" />
<input ng-model="price2" 
       my-test-expression="$model > 10" 
       my-test-expression-watch="price1" 
       required />

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

A expressão de teste possui uma $modelvariável local que você deve usar para compará-la com outras variáveis.

Anteriormente:

Fiz uma tentativa de melhorar o código @Plantface adicionando diretiva extra. Essa diretiva extra é muito útil se nossa expressão precisar ser executada quando forem feitas alterações 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 criar campos validados cruzados:

<input name="price1"
       ng-model="price1" 
       ensure-expression="price1 > price2" 
       ensure-watch="price2" />
<input name="price2" 
       ng-model="price2" 
       ensure-expression="price2 > price3" 
       ensure-watch="price3" />
<input name="price3" 
       ng-model="price3" 
       ensure-expression="price3 > price1 && price3 > price2" 
       ensure-watch="price1,price2" />

ensure-expressioné executado para validar o modelo quando ng-modelou qualquer uma das ensure-watchvariáveis ​​é alterada.

knr
fonte
4

@ sinergético acho que @blesh suponha colocar função 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);
Atul Chaudhary
fonte
4

Validações personalizadas que chamam um servidor

Use a API ngModelController,$asyncValidators que lida com a validação assíncrona, como fazer uma $httpsolicitação ao back-end. As funções adicionadas ao objeto devem retornar uma promessa que deve ser resolvida quando válida ou rejeitada quando inválida. As validações assíncronas em andamento são armazenadas por digitação ngModelController.$pending. Para obter mais informações, consulte o Guia do desenvolvedor do AngularJS - Formulários (validação personalizada) .

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


Usando a $validatorsAPI

A resposta aceita usa os pipelines $parserse $formatterspara adicionar um validador síncrono personalizado. O AngularJS 1.3+ adicionou uma $validatorsAPI para que não seja necessário colocar validadores nos pipelines $parserse $formatters:

app.directive('blacklist', function (){ 
   return {
      require: 'ngModel',
      link: function(scope, elem, attr, ngModel) {           
          ngModel.$validators.blacklist = function(modelValue, viewValue) {
              var blacklist = attr.blacklist.split(',');
              var value = modelValue || viewValue;
              var valid = blacklist.indexOf(value) === -1;
              return valid;
          });    
      }
   };
});

Para obter mais informações, consulte Referência da API do AngularJS ngModelController - $ validators .

georgeawg
fonte
3

No 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 objeto de chave / valor que ele escuta. A própria diretiva complementa o relatório de mensagens de erro com o objeto de erro ngModel $ (que armazena um estado de chave / valor dos erros de validação).

Para validação de formulário personalizado, deve-se usar os módulos ngMessages com diretiva personalizada.

 <form name="myform" novalidate>
                <table>
                    <tr>
                        <td><input name='test' type='text' required  ng-model='test' custom-validation></td>
                        <td ng-messages="myform.test.$error"><span ng-message="invalidshrt">Too Short</span></td>
                    </tr>
                </table>
            </form>

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 função embutida para definir o estado do modelo como válido / inválido

Muhammad Nasir
fonte
1

Estendi a resposta de @Ben Lesh com a capacidade de especificar se a validação diferencia maiúsculas de minúsculas ou não (padrão)

usar:

<input type="text" name="fruitName" ng-model="data.fruitName" blacklist="Coconuts,Bananas,Pears" caseSensitive="true" required/>

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;
                });
            }
        };
    }
]);
Liran Brimer
fonte
0

Alguns ótimos exemplos e bibliotecas apresentados neste tópico, mas eles não tinham o que eu estava procurando. Minha abordagem: angular-validity - uma lib de validação baseada em promessa para validação assíncrona, com o estilo Bootstrap opcional incorporado.

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

<input  type="text" name="field4" ng-model="field4"
        validity="eval"
        validity-eval="!(field1 && field2 && field3 && !field4)"
        validity-message-eval="This field is required">

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

2Carga
fonte