Validação em Angular

A maioria dos dados de entrada do usuário relacionados podem ser coletados no HTML5 <form>.

O Angular reescreve a formdiretiva, faz com que tenha um mecanismo holístico de validação, para que o usuário possa ser notificado de entradas inválidas por exemplo. Se quisermos usar esses recursos do Angular, devemos usar alguns atributos específicos ao lado de forme sua subtag.

Com base em nosso aplicativo da Web de biblioteca pública, tomamos a função de Add a bookcomo um exemplo para apresentar a validação do caminho angular.

Como dissemos, todas as entradas do usuário serão compactadas <form>, aqui está o formato básico da formtag HTML5 no Angular:

<form name="bookInfo" novalidate="novalidate" ng-submit="createBook()">
...

</form>

novalidateé usado para desabilitar a validação de formulário nativo do navegador, pois cada navegador pode ter sua própria maneira de lidar com a validação de formulário. Não é isso que esperamos.

Observe que o formnão é mais a tag HTML5 original, ele foi substituído por Angular e possui um alias ngForm. O que significa que os dois formatos a seguir são iguais:

1. <form name="bookInfo">...</form>
2.
<ng-form name="bookInfo">...</ng-form>

formem Angular é rastreado por form.FormController, que continua observando o estado do formulário, como sendo válido / inválido. Além disso, quando o nameatributo de <form>for especificado, o formulário será publicado no escopo atual com este nome.

Restrições e validações genéricas

  • O formulário nameé “bookInfo”;
  • Um livro tem quatro atributos: ISBN, Título, Ano e Edição. Fazemos cada um como um inputelemento para receber as entradas do usuário.
  • Cada entrada possui um ngModelatributo, será controlada pelo Angular como modelo.

Nosso foco é inputneste momento. O Angular fornece muitos argumentos inputpara vinculação de dados, controle de estado e validação:

  • Valor Obrigatório
    • Restrição: <input type="text" name="isbn" ng-model="book.isbn" required="required" />
    • Validação: bookInfo.isbn.$error.required
    • Descrição: a entrada de ngModel é necessária, não deve estar vazia, caso contrário, o Angular lançará um erro, o valor de será verdadeiro.book.isbnbookInfo.isbn.$error.required
  • padronizar
    • Restrição: <input type="text" name="isbn" ng-model="book.isbn" ng-pattern="/\b\d{9}(\d|X)\b/" />
    • Validação: bookInfo.isbn.$error.pattern
    • Descrição: a entrada de ngModel deve atender ao padrão, caso contrário, o Angular lançará um erro, o valor de será verdadeiro.book.isbn/\b\d{9}(\d|X)\b/bookInfo.isbn.$error.pattern
  • Comprimento da corda
    • Restrição: <input type="text" name="title" ng-model="book.title" ng-maxlength="50" />
    • Validação: bookInfo.title.$error.maxlength
    • Descrição: o comprimento da string de entrada de ngModel deve ser menor que 50, caso contrário, Angular gerará um erro, o valor de será verdadeiro.book.titlebookInfo.title.$error.maxlength
  • Alcance
    • Restrição: <input type="number" name="year" ng-model="book.year" min="1459" max="{{currentYear}}" />
    • Validação: bookInfo.year.$error.number && bookInfo.year.$error.min && bookInfo.year.$error.max
    • Descrição: a entrada de ngModel deve ser um número, o número também deve estar entre 1459 e o ano atual (fale sobre isso mais tarde), caso contrário, o Angular lançará o erro correspondente.book.year

Além disso, para permitir o estilo da forma, ngModelirá adicionar automaticamente algumas classes CSS:

  • ng-valid: a entrada é válida
  • ng-invalid: a entrada é inválida
  • ng-touched: o campo de entrada foi borrado
  • ng-untouched: o campo de entrada não foi desfocado
  • ng-pending: qualquer um $asyncValidatorsestá incompleto

Além disso, o formtambém possui alguns atributos para a validação holística do estado. Tomamos o mais útil $invalidcomo exemplo:

<form name="bookInfo" ng-submit="createBook()">
...

<input type="submit" ng-disabled="bookInfo.$invalid" />
</form>

Quando todas as entradas que pertencem a este formulário forem válidas, o valor de será falso (ao mesmo tempo, seria verdadeiro), então todos os dados podem ser enviados.bookInfo.$invalidbookInfo.$valid

Restrições e validações específicas (Costom)

A restrição e validação genéricas podem resolver a maioria dos requisitos comuns. Mas quando ele não consegue resolver seus problemas, o Angular oferece outra opção – a diretiva personalizada . Com uma diretiva personalizada, podemos adicionar nossas próprias funções de validação ao objeto $validatorsou $asyncValidatorsno ngModelController.

Valor calculado

Anteriormente, implementamos a restrição e validação do ano. A faixa de max é um valor dinâmico que deve ser aumentado a cada ano. Se não quisermos assumir a tarefa de alterar o valor por nós mesmos, é melhor deixar o computador ir assim.

Maneira simples

Como nosso exemplo, definimos uma expressão como o valor do atributo. Sabemos que pode obter o valor do ano atual, então o colocamos no controlador e o atribuímos ao escopo como . Angular levará o valor para o atributo.{{currentYear}}maxnew Date().getFullYear()$scope.currentYear = new Date().getFullYear();

Melhor maneira

O primeiro método é fácil e rápido, mas vai misturar a estrutura de nossa aplicação web. O problema de um DOM deve ser melhor resolvido no lado do template, e este é exatamente o propósito de implementar as diretivas Angular.

As diretivas Angular são marcadores em um elemento DOM (como um atributo, nome de elemento, comentário ou classe CSS) que informam ao compilador HTML do Angular (é como anexar ouvintes de evento ao HTML para torná-lo interativo) para anexar um comportamento específico a esse Elemento DOM ou mesmo transformar o elemento DOM e seus filhos.

Modificamos um pouco nossa definição original de campo de entrada HTML sobre o ano:

<input type="number" name="year" ng-model="book.year" year-period="year-period" />

Aqui, a diretiva Angular é definida pelo usuário. Seria implementado para restringir o ano de publicação do livro – não antes de 1459 e não depois do ano atual:year-period

app.directive('yearPeriod', function() {
return {
restrict: 'A', // the directive will only match attribute name
require
: 'ngModel', // the directive require the ng-model
link
: function(scope, element, attribute, ngModel) { // "link" is defined by Angular to modify the DOM
ngModel
.$validators.yearPeriod = function(modelValue, viewValue) { // “$validators” is a property of ngModelController, also a collection of validators that are applied whenever the model value changes.
var minYear = 1459; // Minimal publish year is 1459
var maxYear = new Date().getFullYear(); // Maximal publish year is this year
if (modelValue < 1459 || modelValue > maxYear) {
return false; // return to DOM a boolean value “bookInfo.year.$error.yearPeriod === true”
} else {
return true; // return to DOM a boolean value “bookInfo.year.$error.yearPeriod === false”
}
}
}
};
});

Valor Único

Armazenamos livros em nosso aplicativo da web, e cada livro tem um valor obrigatório ISBN , o que significa que em nossa biblioteca não deve haver dois ou mais livros com o mesmo isbn. O ISBN deve ser o valor único.

Definimos a entrada de isbn primeiro:

<input type="text" name="isbn" ng-model="book.isbn" ng-pattern="/\b\d{9}(\d|X)\b/" required="required" unique-isbn="unique-isbn" />

Semelhante à Diretiva de Valor Calculado criada acima, percebemos aqui apenas um XMLHTTPRequest assíncrono ao nosso armazenamento em nuvem Parse, que irá verificar o banco de dados, se houver um livro com o mesmo ISBN ou não, após o usuário preencher o campo de entrada do ISBN. Um valor booleano “bookInfo.isbn. $ Error.uniqueIsbn === true” será retornado, se o ISBN existir no banco de dados.

publicLibraryDirectives.directive('uniqueIsbn', ['$q', '$timeout', '$http', function($q, $timeout, $http) {
return {
restrict: 'A',
require
: 'ngModel',
link
: function(scope, element, attribute, ngModel) {
ngModel
.$asyncValidators.uniqueIsbn = function(modelValue, viewValue) {
var defer = $q.defer(); // $q.defer() is used to expose the associated Promise for signaling the successful or unsuccessful completion as well as the status of the task
$timeout
(function() { // $timeout is Angular’s wapper for window.setTimeout
$http
({ // Send a GET request to Parse.com
method
: 'GET',
url
: 'https://api.parse.com/1/classes/Book',
params
: {
where
: {'isbn': modelValue}
},
headers
:{
'X-Parse-Application-Id': Application_ID,
'X-Parse-REST-API-Key': REST_API_Key,
}
})
.success(function(data, status, headers, config){ // When request is success, get response message from server
if (data.results.length === 0) { // If there is no same ISBN in the database, the response’s array should be null
defer
.resolve();
} else { // otherwise the ISBN exists
defer
.reject();
}
})
.error(function(data, status, headers, config){ // The connection with database went wrong
console
.log("something went wrong...");
});
}, 2000); // Set a time delay
return defer.promise; // return a boolean value to view side after finished the XMLHTTPRequest
}
}
};
}]);

Maneira diferente de exibir mensagens de erro

Nesta etapa, avançamos em direção às diferentes maneiras de exibir mensagens de erro na visualização.

O Angular oferece várias soluções, ou melhor, diretivas para lidar com essa tarefa, os formulários populares são ngShow, ngIfe uma nova diretiva ngMessagesno Angular v1.3.x

Discutimos muito sobre as restrições de entrada e validações acima; lembre-se de que o Angular retornará um valor booleano para uma variável definida pelo sistema para cada validação que depende da restrição.ngModel.$errorngModel

Para a comparação a seguir, usamos o campo de entrada do ano como exemplo:

<input type="number" name="year" ng-model="book.year" year-period="year-period" required="required" />

A entrada de ngModel é necessária, o valor deve ser um número e o número não deve ser inferior a 1459 ou superior a 2015 (ano atual como um número).book.year

ng-show

A ngShowdiretiva mostra ou oculta o elemento HTML fornecido com base na expressão fornecida ao ngShowatributo. O elemento é mostrado ou oculto removendo ou adicionando a classe CSS ao elemento. Portanto, a verdade é que o conteúdo deste elemento será carregado com a página da web, e o CSS controlá-lo-á para aparecer ou ficar oculto..ng-hide

Exibir mensagens de erro usando ngShow:

<input type="number" name="year" ng-model="book.year" year-period="year-period" required="required" />
<span ng-show="bookInfo.year.$error.required">Year is required.</span>
<span ng-show="bookInfo.year.$error.number">Not valid number.</span>
<span ng-show="bookInfo.year.$error.yearPeriod">The publish time should be between 1459 and this year.</span>

Quando a página for carregada, todas spantambém serão carregadas, e podemos ver seu conteúdo “O ano é obrigatório”. existia apenas porque o Angular detecta que atualmente o campo de entrada está vazio ou melhor, o valor de é verdadeiro, então o conteúdo dentro de deve aparecer. Depois de colocarmos algumas letras no campo, “O ano é obrigatório”. ficará oculto.bookInfo.year.$error.requiredspan

behavior-ngShow

O spanatributo with e são semelhantes ao atributo with .ng-show="bookInfo.year.$error.number"ng-show="bookInfo.year.$error.yearPeriod"spanng-show="bookInfo.year.$error.required

Observe que o spanestá apenas oculto, mas ainda existe no DOM. Esta é exatamente a parte diferente entre ngShowe ngIf.

ng-if

A ngIfdiretiva remove ou recria uma parte da árvore DOM com base em uma expressão. Se a expressão atribuída a for ngIfavaliada como um valor falso, o elemento será removido do DOM, caso contrário, um clone do elemento será reinserido no DOM.

Exibir mensagens de erro usando ngIf:

<input type="number" name="year" ng-model="book.year" year-period="year-period" required="required" />
<span ng-if="bookInfo.year.$error.required">Year is required.</span>
<span ng-if="bookInfo.year.$error.number">Not valid number.</span>
<span ng-if="bookInfo.year.$error.yearPeriod">The publish time should be between 1459 and this year.</span>

Quando a página é carregada, podemos ver seu conteúdo “O ano é obrigatório”. existia. Parece que está usando ngShowporque atualmente o valor de é true, fez um clone de e inseriu no DOM, de modo que o conteúdo dentro de é mostrado. Depois de colocarmos algumas letras no campo, o valor de seria falso e removerá o para que “Ano seja obrigatório”. vai desaparecer.bookInfo.year.$error.requiredngIfspanspanbookInfo.year.$error.requiredngIfspan

comportamento-ngIf

O spanatributo with e são semelhantes ao atributo with .ng-if="bookInfo.year.$error.number"ng-if="bookInfo.year.$error.yearPeriod"spanng-if="bookInfo.year.$error.required"

ngIfcontrola o elemento que é parte da árvore DOM enquanto ngShowaltera o comportamento do site via uma propriedade CSS.

Por outro lado, uma vez que todas as diretivas correspondentes ao elemento DOM tenham sido identificadas, o compilador Angular classificará as diretivas por sua prioridade . ngIftem uma das prioridades mais altas (nível 600 por padrão), ele será executado primeiro, antes de todas as outras diretivas de prioridade inferior. E se o usarmos em vez de ngShow, a IU será bem acelerada [^ 1].

[^ 1]: Não encontrei uma ferramenta para testar a velocidade, a informação veio de um post de stackoverflow de gjoris .

ng-messages

O ngMessagesé um módulo Angular publicado pela versão 1.3.x do Angular. Ele contém ngMessagese ngMessagediretrizes. Ele fornece especificamente suporte aprimorado para a exibição de mensagens, como mensagens de erro, nos modelos. Em vez de depender de instruções complexas em nosso modelo de formulário para mostrar e ocultar mensagens de erro específicas para o estado de um campo de entrada.ng-if

As tarefas de duas diretivas são:

  • ngMessages: foi projetado para mostrar e ocultar mensagens com base no estado de um objeto de chave / valor que ele escuta, e a própria diretiva complementa o relatório de mensagem de erro com o $errorobjeto. Por padrão, apenas uma mensagem será exibida por vez, mas isso pode ser alterado usando no contêiner de diretiva. E usando o modelo especificado pode ser incluído no contêiner ng-messages.ng-messages-multipleng-messages-include
  • ngMessage: tem como objetivo mostrar e ocultar uma determinada mensagem. Para ngMessageoperar, uma ngMessagesdiretiva pai em um elemento DOM pai deve ser situada, uma vez que determina quais mensa