Filter auf ng-Modell in einer Eingabe

124

Ich habe eine Texteingabe und möchte nicht, dass Benutzer Leerzeichen verwenden, und alles, was eingegeben wird, wird in Kleinbuchstaben umgewandelt.

Ich weiß, dass ich keine Filter für ng-Modelle verwenden darf, z.

ng-model='tags | lowercase | no_spaces'

Ich wollte meine eigene Direktive erstellen, aber Funktionen hinzufügen $parsersund $formattersdie Eingabe nicht aktualisieren, sondern nur andere Elemente, die ng-modeldarauf enthalten waren.

Wie kann ich die Eingabe ändern, die ich gerade eingebe?

Ich versuche im Wesentlichen, die 'Tags'-Funktion zu erstellen, die genau wie die hier bei StackOverflow funktioniert.

Andrew WC Brown
quelle
Überprüfen
Mark Rajcok

Antworten:

28

Ich würde vorschlagen, den Modellwert zu beobachten und ihn zu aktualisieren: http://plnkr.co/edit/Mb0uRyIIv1eK8nTg3Qng?p=preview

Das einzig interessante Problem betrifft Leerzeichen: In AngularJS 1.0.3 schneidet ng-model bei der Eingabe die Zeichenfolge automatisch ab, sodass nicht erkannt wird, dass das Modell geändert wurde, wenn Sie am Ende oder am Anfang Leerzeichen hinzufügen (sodass Leerzeichen von my nicht automatisch entfernt werden Code). In 1.1.1 gibt es jedoch die Anweisung 'ng-trim', mit der diese Funktionalität deaktiviert werden kann ( Festschreiben ). Daher habe ich mich für 1.1.1 entschieden, um die genaue Funktionalität zu erreichen, die Sie in Ihrer Frage beschrieben haben.

Valentyn Shybanov
quelle
Genau das habe ich gesucht. Es stellt sich heraus, dass ich bereits anglejs 1.1.1
Andrew WC Brown am
@Valentyn, Ihre Lösung wurde auf die SO-Frage angewendet, auf die ich im obigen Kommentar verwiesen habe. Vielen Dank. stackoverflow.com/questions/12176925/…
Mark Rajcok
Diese Lösung kann schlimme Nebenwirkungen haben, siehe andere Antwort unten, Sie sollten eine Anweisung dafür verwenden
pilavdzice
Durch das Neuzuweisen der Bereichsvariablen von innen $watchwird der Listener erneut aufgerufen. In einfachen Fällen (in denen Ihr Filter idempotent ist) wird der Filter bei jeder Änderung zweimal ausgeführt.
Inkarnation
204

Ich glaube, dass die Absicht von AngularJS-Eingaben und der ngModelAnweisung ist, dass ungültige Eingaben niemals im Modell landen sollten . Das Modell sollte immer gültig sein. Das Problem mit einem ungültigen Modell besteht darin, dass wir möglicherweise Beobachter haben, die auf der Grundlage eines ungültigen Modells feuern und (unangemessene) Maßnahmen ergreifen.

Aus meiner Sicht besteht die richtige Lösung darin, die $parsersPipeline anzuschließen und sicherzustellen, dass ungültige Eingaben nicht in das Modell gelangen. Ich bin mir nicht sicher, wie Sie versucht haben, Dinge anzugehen oder was genau bei Ihnen nicht funktioniert hat, $parsersaber hier ist eine einfache Anweisung, die Ihr Problem (oder zumindest mein Verständnis des Problems) löst:

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

Sobald die obige Richtlinie deklariert ist, kann sie wie folgt verwendet werden:

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

Wie in der von @Valentyn Shybanov vorgeschlagenen Lösung müssen wir die ng-trimDirektive verwenden, wenn Leerzeichen am Anfang / Ende der Eingabe nicht zugelassen werden sollen.

Der Vorteil dieses Ansatzes ist zweifach:

  • Ein ungültiger Wert wird nicht an das Modell weitergegeben
  • Mit einer Direktive ist es einfach, diese benutzerdefinierte Validierung zu jeder Eingabe hinzuzufügen, ohne die Beobachter immer wieder zu duplizieren
pkozlowski.opensource
quelle
1
Ich bin sicher, dass ein kniffliger Teil mit modelCtrl.$setViewValue(transformedInput); modelCtrl.$render();Nützlichem Link zur Dokumentation verbunden war: docs.angularjs.org/api/ng.directive:ngModel.NgModelController Ein Wort, um meine Lösung zu "schützen", ist, dass die Scope-Eigenschaft nicht nur vor Ansichten und geändert werden kann Mein Weg deckt dies ab. Ich denke, es hängt von einer tatsächlichen Situation ab, wie der Umfang geändert werden könnte.
Valentyn Shybanov
2
Worauf bezieht sich 'modelCtrl' in Ihrem Beispiel?
GSto
4
Woher bekommen Sie den inputValue?
Dofs
2
@GSto modelCtrlist der von der Direktive geforderte Controller. ( require 'ngModel')
Nate-Wilkins
7
Der Cursor springt jedes Mal an das Ende des Textfelds, wenn Sie ein ungültiges Zeichen eingeben. Versuchen Sie, 'world' zu schreiben und in 'HeLLo world' zu ändern!
Hafez Divandari
23

Eine Lösung für dieses Problem könnte darin bestehen, die Filter auf der Controllerseite anzuwenden:

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

Vergessen Sie nicht, $filterals Abhängigkeit zu deklarieren .

Pierre-Yves Le Dévéhat
quelle
4
Sie benötigen jedoch eine $ watch, wenn Sie möchten, dass es ordnungsgemäß aktualisiert wird.
Herr Mikkél
Dies wird nur einmal ausgeführt. Das Hinzufügen zu einer Uhr ist nicht die richtige Lösung, da das Modell bereits anfänglich ungültig wird. Die richtige Lösung besteht darin, die $ -Parser des Modells zu ergänzen.
icfantv
4
Du musst meine Antwort nicht mögen, aber das heißt nicht, dass es falsch ist. Überprüfen Sie Ihr Ego, bevor Sie abstimmen.
icfantv
6

Wenn Sie ein schreibgeschütztes Eingabefeld verwenden, können Sie den ng-Wert mit Filter verwenden.

beispielsweise:

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

Verwenden Sie eine Direktive, die sowohl die Auflistungen $ formatters als auch $ parsers ergänzt, um sicherzustellen, dass die Transformation in beide Richtungen ausgeführt wird.

In dieser anderen Antwort finden Sie weitere Details, einschließlich eines Links zu jsfiddle.

Scott Munro
quelle
3

Ich hatte ein ähnliches Problem und benutzte

ng-change="handler(objectInScope)" 

In meinem Handler rufe ich eine Methode des objectInScope auf, um sich selbst korrekt zu ändern (grobe Eingabe). In der Steuerung habe ich irgendwo das initiiert

$scope.objectInScope = myObject; 

Ich weiß, dass hier keine ausgefallenen Filter oder Beobachter verwendet werden ... aber es ist einfach und funktioniert hervorragend. Der einzige Nachteil dabei ist, dass das objectInScope beim Aufruf an den Handler gesendet wird ...

wojjas
quelle
1

Wenn Sie eine komplexe asynchrone Eingabevalidierung durchführen, kann es sich lohnen ng-model, eine Ebene als Teil einer benutzerdefinierten Klasse mit eigenen Validierungsmethoden zu abstrahieren .

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

Sie können dies versuchen

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

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

});
Nikhil Mahirrao
quelle