filtros en ng-model en una entrada

124

Tengo una entrada de texto y no quiero permitir que los usuarios usen espacios, y todo lo que se escriba se convertirá en minúsculas.

Sé que no puedo usar filtros en ng-model, por ejemplo.

ng-model='tags | lowercase | no_spaces'

Busqué crear mi propia directiva pero agregar funciones $parsersy $formattersno actualicé la entrada, solo otros elementos que tenía ng-model.

¿Cómo puedo cambiar la entrada de la que estoy escribiendo actualmente?

Básicamente, estoy tratando de crear la función de 'etiquetas' que funciona igual que la aquí en StackOverflow.

Andrew WC Brown
fuente
Vea si usar $ timeout (..., 0) con ng-change ayuda: stackoverflow.com/questions/12176925/…
Mark Rajcok

Respuestas:

28

Sugeriría ver el valor del modelo y actualizarlo al cambiar: http://plnkr.co/edit/Mb0uRyIIv1eK8nTg3Qng?p=preview

El único problema interesante es con los espacios: en AngularJS 1.0.3 ng-model en la entrada recorta automáticamente la cadena, por lo que no detecta que el modelo se cambió si agrega espacios al final o al inicio (por lo que mis espacios no se eliminan automáticamente código). Pero en 1.1.1 hay una directiva 'ng-trim' que permite deshabilitar esta funcionalidad ( commit ). Así que decidí usar 1.1.1 para lograr la funcionalidad exacta que describiste en tu pregunta.

Valentyn Shybanov
fuente
Esto fue exactamente lo que estaba buscando. Resulta que ya estoy usando angularjs 1.1.1
Andrew WC Brown
@Valentyn, su solución se aplicó a la pregunta SO a la que hice referencia en el comentario anterior. Gracias. stackoverflow.com/questions/12176925/…
Mark Rajcok
esta solución puede tener efectos secundarios negativos, vea otra respuesta a continuación, debe usar una directiva para esto
pilavdzice
La reasignación de la variable de alcance desde dentro $watchobliga al oyente a ser invocado nuevamente. En casos simples (donde su filtro es idempotente), terminará con el filtro ejecutándose dos veces en cada modificación.
Encarnado el
204

Creo que la intención de las entradas de AngularJS y la ngModeldireccional es que la entrada no válida nunca termine en el modelo . El modelo siempre debe ser válido. El problema con tener un modelo no válido es que podríamos tener observadores que disparen y tomen acciones (inapropiadas) basadas en un modelo no válido.

Tal como lo veo, la solución adecuada aquí es conectarse a la $parserstubería y asegurarse de que la entrada no válida no llegue al modelo. No estoy seguro de cómo trató de abordar las cosas o con qué no funcionó exactamente para usted, $parserspero aquí hay una directiva simple que resuelve su problema (o al menos mi comprensión del 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;         
       });
     }
   };
});

Tan pronto como se declare la directiva anterior, se puede usar así:

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

Como en la solución propuesta por @Valentyn Shybanov, necesitamos usar la ng-trimdirectiva si queremos no permitir espacios al principio / al final de la entrada.

La ventaja de este enfoque es doble:

  • El valor no válido no se propaga al modelo.
  • Con una directiva, es fácil agregar esta validación personalizada a cualquier entrada sin duplicar observadores una y otra vez
pkozlowski.opensource
fuente
1
Estoy seguro de que la parte difícil con modelCtrl.$setViewValue(transformedInput); modelCtrl.$render();Útil sería un enlace a la documentación: docs.angularjs.org/api/ng.directive:ngModel.NgModelController Una palabra para "proteger" mi solución es que la propiedad del alcance podría cambiarse no solo desde las vistas y mi manera cubre esto. Así que creo que depende de una situación real cómo se podría modificar el alcance.
Valentyn Shybanov
2
¿A qué se refiere 'modelCtrl' en su ejemplo?
GSto
44
¿De dónde obtienes el inputValue?
Dofs
2
@GSto modelCtrles el controlador requerido por la directiva. ( require 'ngModel')
Nate-Wilkins
77
¡El cursor salta al final del campo de texto cada vez que escribe un carácter no válido, intente escribir 'mundo' y modificarlo a 'Mundo HeLLo'!
Hafez Divandari
23

Una solución a este problema podría ser aplicar los filtros en el lado del controlador:

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

No olvides declarar $filtercomo dependencia.

Pierre-Yves Le Dévéhat
fuente
44
Pero necesitarías un $ watch si quieres que se actualice correctamente.
Sr. Mikkél
esto solo se ejecuta una vez. y agregar a un reloj no es la solución correcta porque, incluso inicialmente, permite que el modelo deje de ser válido; la solución correcta es agregar a los $ parsers del modelo.
icfantv
44
No tiene que gustarle mi respuesta, pero eso no significa que esté mal. Comprueba tu ego antes de votar en contra.
icfantv
6

Si está utilizando un campo de entrada de solo lectura, puede usar ng-value con filtro.

por ejemplo:

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

Use una directiva que se agregue a las colecciones $ formateadores y $ parsers para garantizar que la transformación se realice en ambas direcciones.

Consulte esta otra respuesta para obtener más detalles, incluido un enlace a jsfiddle.

Scott Munro
fuente
3

Tuve un problema similar y usé

ng-change="handler(objectInScope)" 

en mi controlador, llamo a un método del objectInScope para modificarse correctamente (entrada aproximada). En el controlador he iniciado en algún lugar que

$scope.objectInScope = myObject; 

Sé que esto no usa ningún filtro o observador sofisticado ... pero es simple y funciona muy bien. El único inconveniente de esto es que el objectInScope se envía en la llamada al controlador ...

wojjas
fuente
1

Si está haciendo una validación de entrada compleja y asíncrona, puede valer la pena resumir ng-modelun nivel como parte de una clase personalizada con sus propios métodos de validación.

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
fuente
0

Puedes probar esto

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

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

});
Nikhil Mahirrao
fuente