filtros no modelo ng em uma entrada

124

Eu tenho uma entrada de texto e não quero permitir que os usuários usem espaços, e tudo que for digitado será transformado em minúsculas.

Eu sei que não tenho permissão para usar filtros no modelo ng, por exemplo.

ng-model='tags | lowercase | no_spaces'

Eu olhei para criar minha própria diretiva, mas adicionando funções $parserse $formattersnão atualizei a entrada, apenas outros elementos que continham ng-modelnela.

Como posso alterar a entrada que estou digitando no momento?

Estou essencialmente tentando criar o recurso 'tags' que funciona exatamente como o do StackOverflow.

Andrew WC Brown
fonte
Veja se o uso de $ timeout (..., 0) com ng-change ajuda: stackoverflow.com/questions/12176925/…
Mark Rajcok

Respostas:

28

Eu sugeriria observar o valor do modelo e atualizá-lo mediante alteração: http://plnkr.co/edit/Mb0uRyIIv1eK8nTg3Qng?p=preview

O único problema interessante é com espaços: no AngularJS 1.0.3 ng-model, a entrada apara automaticamente a sequência, para que ele não detecte que o modelo foi alterado se você adicionar espaços no final ou no início (para que os espaços não sejam removidos automaticamente pelo meu código). Mas na 1.1.1 existe a diretiva 'ng-trim' que permite desativar essa funcionalidade ( confirmação ). Decidi usar o 1.1.1 para obter a funcionalidade exata que você descreveu na sua pergunta.

Valentyn Shybanov
fonte
Era exatamente isso que eu procurava. Acontece que eu já estou usando AngularJS 1.1.1
Andrew WC Brown
@ Valentyn, sua solução foi aplicada à pergunta SO mencionada no comentário acima. Obrigado. stackoverflow.com/questions/12176925/…
Mark Rajcok
esta solução pode ter efeitos colaterais ruins, ver outra resposta abaixo, você deve usar uma directiva para este
pilavdzice
A reatribuição da variável de escopo de dentro $watchforça o ouvinte a ser chamado novamente. Em casos simples (onde seu filtro é idempotente), você terminará com o filtro executando duas vezes em cada modificação.
encarnada
204

Acredito que a intenção das entradas AngularJS e a ngModeldiretiva é que as entradas inválidas nunca terminem no modelo . O modelo deve sempre ser válido. O problema de ter um modelo inválido é que podemos ter observadores que acionam e executam ações (inadequadas) com base no modelo inválido.

A meu ver, a solução adequada aqui é conectar-se ao $parserspipeline e garantir que entradas inválidas não entrem no modelo. Não tenho certeza de como você tentou abordar as coisas ou o que exatamente não funcionou para você, $parsersmas aqui está uma diretiva simples que resolve seu problema (ou pelo menos minha compreensão do problema):

app.directive('customValidation', function(){
   return {
     require: 'ngModel',
     link: function(scope, element, attrs, modelCtrl) {

       modelCtrl.$parsers.push(function (inputValue) {

         var transformedInput = inputValue.toLowerCase().replace(/ /g, ''); 

         if (transformedInput!=inputValue) {
           modelCtrl.$setViewValue(transformedInput);
           modelCtrl.$render();
         }         

         return transformedInput;         
       });
     }
   };
});

Assim que a diretiva acima for declarada, ela poderá ser usada da seguinte maneira:

<input ng-model="sth" ng-trim="false" custom-validation>

Como na solução proposta por @Valentyn Shybanov, precisamos usar a ng-trimdiretiva se queremos proibir espaços no início / final da entrada.

A vantagem dessa abordagem é 2 vezes:

  • Valor inválido não é propagado para o modelo
  • Usando uma diretiva, é fácil adicionar essa validação personalizada a qualquer entrada sem duplicar os observadores repetidamente
pkozlowski.opensource
fonte
1
Tenho certeza de que a parte mais complicada da seçãomodelCtrl.$setViewValue(transformedInput); modelCtrl.$render(); Útil seria o link para a documentação: docs.angularjs.org/api/ng.directive:ngModel.NgModelController Uma palavra para "proteger" minha solução é que a propriedade do escopo possa ser alterada não apenas de visualizações e meu caminho cobre isso. Então eu acho que depende de uma situação real como o escopo pode ser modificado.
Valentyn Shybanov 26/01
2
a que 'modelCtrl' se refere no seu exemplo?
GSto
4
De onde você obtém o inputValue?
Dofs 23/05
2
@GSto modelCtrlé o controlador exigido pela diretiva. ( require 'ngModel')
Nate-Wilkins
7
O cursor salta para o final do campo de texto toda vez que você digita um caractere inválido, tente escrever 'world' e modifique-o para 'HeLLo world'!
Hafez Divandari
23

Uma solução para esse problema pode ser aplicar os filtros no lado do controlador:

$scope.tags = $filter('lowercase')($scope.tags);

Não se esqueça de declarar $filtercomo dependência.

Pierre-Yves Le Dévéhat
fonte
4
Mas você precisaria de um relógio $ se quiser atualizar corretamente.
Mikkél
isso é executado apenas uma vez. e adicionar a um relógio não é a solução certa, pois, mesmo inicialmente, permite que o modelo se torne inválido - a solução correta é adicionar aos analisadores de $ $ do modelo.
22816 icfantv
4
Você não precisa gostar da minha resposta, mas isso não significa que está errado. Verifique seu ego antes de votar.
28816 icfantv
6

Se você estiver usando o campo de entrada somente leitura, poderá usar o valor ng com o filtro.

por exemplo:

ng-value="price | number:8"
Edward D. Wilson
fonte
4

Use uma diretiva que inclua as coleções $ formatadores e $ parsers para garantir que a transformação seja executada nas duas direções.

Veja esta outra resposta para obter mais detalhes, incluindo um link para jsfiddle.

Scott Munro
fonte
3

Eu tive um problema semelhante e usei

ng-change="handler(objectInScope)" 

no meu manipulador, chamo um método do objectInScope para modificar-se corretamente (entrada grossa). No controlador, iniciei em algum lugar que

$scope.objectInScope = myObject; 

Eu sei que isso não usa filtros ou observadores sofisticados ... mas é simples e funciona muito bem. A única desvantagem disso é que o objectInScope é enviado na chamada para o manipulador ...

wojjas
fonte
1

Se você estiver executando validação de entrada assíncrona complexa, pode valer a pena abstrair ng-modelum nível como parte de uma classe personalizada com seus próprios métodos de validação.

https://plnkr.co/edit/gUnUjs0qHQwkq2vPZlpO?p=preview

html

<div>

  <label for="a">input a</label>
  <input 
    ng-class="{'is-valid': vm.store.a.isValid == true, 'is-invalid': vm.store.a.isValid == false}"
    ng-keyup="vm.store.a.validate(['isEmpty'])"
    ng-model="vm.store.a.model"
    placeholder="{{vm.store.a.isValid === false ? vm.store.a.warning : ''}}"
    id="a" />

  <label for="b">input b</label>
  <input 
    ng-class="{'is-valid': vm.store.b.isValid == true, 'is-invalid': vm.store.b.isValid == false}"
    ng-keyup="vm.store.b.validate(['isEmpty'])"
    ng-model="vm.store.b.model"
    placeholder="{{vm.store.b.isValid === false ? vm.store.b.warning : ''}}"
    id="b" />

</div>

código

(function() {

  const _ = window._;

  angular
    .module('app', [])
    .directive('componentLayout', layout)
    .controller('Layout', ['Validator', Layout])
    .factory('Validator', function() { return Validator; });

  /** Layout controller */

  function Layout(Validator) {
    this.store = {
      a: new Validator({title: 'input a'}),
      b: new Validator({title: 'input b'})
    };
  }

  /** layout directive */

  function layout() {
    return {
      restrict: 'EA',
      templateUrl: 'layout.html',
      controller: 'Layout',
      controllerAs: 'vm',
      bindToController: true
    };
  }

  /** Validator factory */  

  function Validator(config) {
    this.model = null;
    this.isValid = null;
    this.title = config.title;
  }

  Validator.prototype.isEmpty = function(checkName) {
    return new Promise((resolve, reject) => {
      if (/^\s+$/.test(this.model) || this.model.length === 0) {
        this.isValid = false;
        this.warning = `${this.title} cannot be empty`;
        reject(_.merge(this, {test: checkName}));
      }
      else {
        this.isValid = true;
        resolve(_.merge(this, {test: checkName}));
      }
    });
  };

  /**
   * @memberof Validator
   * @param {array} checks - array of strings, must match defined Validator class methods
   */

  Validator.prototype.validate = function(checks) {
    Promise
      .all(checks.map(check => this[check](check)))
      .then(res => { console.log('pass', res)  })
      .catch(e => { console.log('fail', e) })
  };

})();
Daniel Lizik
fonte
0

Você pode tentar isso

$scope.$watch('tags ',function(){

    $scope.tags = $filter('lowercase')($scope.tags);

});
Nikhil Mahirrao
fonte