filtres sur ng-model dans une entrée

124

J'ai une entrée de texte et je ne veux pas autoriser les utilisateurs à utiliser des espaces, et tout ce qui est tapé sera transformé en minuscules.

Je sais que je ne suis pas autorisé à utiliser des filtres sur le modèle ng, par exemple.

ng-model='tags | lowercase | no_spaces'

J'ai regardé créer ma propre directive mais ajouter des fonctions $parserset $formattersne pas mettre à jour l'entrée, seulement d'autres éléments qui y étaient ng-model.

Comment puis-je modifier l'entrée de ce que je suis en train de saisir?

J'essaie essentiellement de créer la fonctionnalité «balises» qui fonctionne exactement comme celle ici sur StackOverflow.

Andrew WC Brown
la source
Voir si l'utilisation de $ timeout (..., 0) avec ng-change aide: stackoverflow.com/questions/12176925/...
Mark Rajcok

Réponses:

28

Je suggérerais de regarder la valeur du modèle et de la mettre à jour lors du changement: http://plnkr.co/edit/Mb0uRyIIv1eK8nTg3Qng?p=preview

Le seul problème intéressant concerne les espaces: dans AngularJS 1.0.3 ng-model à l'entrée coupe automatiquement la chaîne, il ne détecte donc pas que le modèle a été modifié si vous ajoutez des espaces à la fin ou au début (les espaces ne sont donc pas automatiquement supprimés par mon code). Mais en 1.1.1 il y a une directive 'ng-trim' qui permet de désactiver cette fonctionnalité ( commit ). J'ai donc décidé d'utiliser 1.1.1 pour obtenir la fonctionnalité exacte que vous avez décrite dans votre question.

Valentyn Shybanov
la source
C'était exactement ce que je cherchais. Il s'avère que j'utilise déjà angularjs 1.1.1
Andrew WC Brown
@Valentyn, votre solution appliquée à la question SO à laquelle j'ai fait référence dans le commentaire ci-dessus. Merci. stackoverflow.com/questions/12176925/…
Mark Rajcok
cette solution peut avoir de mauvais effets secondaires, voir l'autre réponse ci-dessous, vous devriez utiliser une directive pour cela
pilavdzice
La réattribution de la variable d'étendue depuis l'intérieur $watchforce l'écouteur à être à nouveau appelé. Dans les cas simples (où votre filtre est idempotent), vous vous retrouverez avec le filtre s'exécutant deux fois à chaque modification.
incarné
204

Je crois que l'intention des entrées AngularJS et de la ngModeldirective est qu'une entrée invalide ne devrait jamais se retrouver dans le modèle . Le modèle doit toujours être valide. Le problème avec un modèle invalide est que nous pourrions avoir des observateurs qui déclenchent et prennent des actions (inappropriées) basées sur un modèle invalide.

Selon moi, la solution appropriée ici est de se brancher sur le $parserspipeline et de s'assurer que l'entrée invalide ne parvient pas à entrer dans le modèle. Je ne sais pas comment avez-vous essayé d'aborder les choses ou avec quoi exactement n'a pas fonctionné pour vous, $parsersmais voici une directive simple qui résout votre problème (ou du moins ma compréhension du problème):

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

Dès que la directive ci-dessus est déclarée, elle peut être utilisée comme suit:

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

Comme dans la solution proposée par @Valentyn Shybanov, nous devons utiliser la ng-trimdirective si nous voulons interdire les espaces au début / à la fin de l'entrée.

L'avantage de cette approche est double:

  • Une valeur non valide n'est pas propagée au modèle
  • En utilisant une directive, il est facile d'ajouter cette validation personnalisée à n'importe quelle entrée sans dupliquer les observateurs encore et encore
pkozlowski.opensource
la source
1
Je suis sûr que la partie délicate était avec modelCtrl.$setViewValue(transformedInput); modelCtrl.$render();Useful serait un lien vers la documentation: docs.angularjs.org/api/ng.directive:ngModel.NgModelController Un mot pour "protéger" ma solition est que la propriété scope pourrait être modifiée non seulement à partir des vues et ma façon de couvrir cela. Je pense donc que la manière dont la portée pourrait être modifiée dépend d'une situation réelle.
Valentyn Shybanov le
2
à quoi fait référence «modelCtrl» dans votre exemple?
GSto
4
D'où obtenez-vous la valeur d'entrée?
Dofs
2
@GSto modelCtrlest le contrôleur requis par la directive. ( require 'ngModel')
Nate-Wilkins
7
Le curseur saute à la fin du champ de texte à chaque fois que vous tapez un caractère invalide, essayez d'écrire «monde» et de le modifier en «HeLLo monde»!
Hafez Divandari
23

Une solution à ce problème pourrait être d'appliquer les filtres côté contrôleur:

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

N'oubliez pas de déclarer $filtercomme dépendance.

Pierre-Yves Le Dévéhat
la source
4
Mais vous aurez besoin d'une $ watch dessus si vous voulez qu'il se mette à jour correctement.
Mr Mikkél
ceci n'est exécuté qu'une seule fois. et l'ajout à une montre n'est pas la bonne solution car, même au départ, cela permet au modèle de devenir invalide - la bonne solution est d'ajouter aux analyseurs $ du modèle.
icfantv
4
Vous n'êtes pas obligé d'aimer ma réponse, mais cela ne veut pas dire que c'est faux. Vérifiez votre ego avant de voter.
icfantv
6

Si vous utilisez un champ d'entrée en lecture seule, vous pouvez utiliser ng-value avec filter.

par exemple:

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

Utilisez une directive qui ajoute à la fois aux collections $ formatters et $ parsers pour vous assurer que la transformation est effectuée dans les deux sens.

Voir cette autre réponse pour plus de détails, y compris un lien vers jsfiddle.

Scott Munro
la source
3

J'ai eu un problème similaire et utilisé

ng-change="handler(objectInScope)" 

dans mon gestionnaire, j'appelle une méthode de l'objetInScope pour se modifier correctement (entrée grossière). Dans le contrôleur, j'ai initié quelque part que

$scope.objectInScope = myObject; 

Je sais que cela n'utilise pas de filtres ou d'observateurs sophistiqués ... mais c'est simple et fonctionne très bien. Le seul inconvénient est que objectInScope est envoyé dans l'appel au gestionnaire ...

wojjas
la source
1

Si vous effectuez une validation d'entrée asynchrone complexe, il peut valoir la peine d'abstraire ng-modelun niveau dans le cadre d'une classe personnalisée avec ses propres méthodes de validation.

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>

code

(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
la source
0

Tu peux essayer ça

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

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

});
Nikhil Mahirrao
la source