filtry na modelu ng na wejściu

124

Mam wpisywanie tekstu i nie chcę, aby użytkownicy mogli używać spacji, a wszystko, co zostanie wpisane, zostanie zamienione na małe litery.

Wiem, że nie wolno mi używać filtrów w modelu ng, np.

ng-model='tags | lowercase | no_spaces'

Patrzyłem na stworzenie własnej dyrektywy, ale dodawanie funkcji do wejścia $parsersi $formattersnie aktualizowanie danych wejściowych, tylko inne elementy, które się ng-modelna nim znajdowały.

Jak mogę zmienić dane wejściowe, które aktualnie piszę?

Zasadniczo próbuję utworzyć funkcję „tagów”, która działa tak samo, jak ta tutaj w StackOverflow.

Andrew WC Brown
źródło
Sprawdź, czy użycie $ timeout (..., 0) z ng-change pomaga: stackoverflow.com/questions/12176925/…
Mark Rajcok

Odpowiedzi:

28

Proponuję obserwować wartość modelu i aktualizować ją po zmianie: http://plnkr.co/edit/Mb0uRyIIv1eK8nTg3Qng?p=preview

Jedynym interesującym problemem są spacje: w AngularJS 1.0.3 ng-model na wejściu automatycznie przycina ciąg, więc nie wykrywa, że ​​model został zmieniony, jeśli dodasz spacje na końcu lub na początku (więc spacje nie są automatycznie usuwane przez kod). Ale w 1.1.1 jest dyrektywa 'ng-trim', która pozwala na wyłączenie tej funkcjonalności ( zatwierdzenie ). Dlatego zdecydowałem się użyć wersji 1.1.1, aby uzyskać dokładną funkcjonalność, którą opisałeś w swoim pytaniu.

Valentyn Shybanov
źródło
To było dokładnie to, czego szukałem. Okazuje się, że używam już angularjs 1.1.1
Andrew WC Brown,
@Valentyn, Twoje rozwiązanie dotyczy pytania SO, o którym wspomniałem w powyższym komentarzu. Dzięki. stackoverflow.com/questions/12176925/…
Mark Rajcok
to rozwiązanie może mieć złe skutki uboczne, zobacz inną odpowiedź poniżej, powinieneś użyć do tego dyrektywy
pilavdzice
Ponowne przypisanie zmiennej zakresu z wewnątrz $watchwymusza ponowne wywołanie detektora. W prostych przypadkach (gdy twój filtr jest idempotentny) skończy się to, że filtr będzie wykonywał się dwukrotnie przy każdej modyfikacji.
wcielenie
204

Uważam, że intencją danych wejściowych i ngModeldyrektywy AngularJS jest, aby nieprawidłowe dane wejściowe nigdy nie trafiały do modelu . Model powinien być zawsze aktualny. Problem z posiadaniem nieprawidłowego modelu polega na tym, że możemy mieć obserwatorów, którzy odpalają i podejmują (nieodpowiednie) działania w oparciu o nieprawidłowy model.

Jak widzę, właściwym rozwiązaniem jest tutaj podłączenie do $parserspotoku i upewnienie się, że nieprawidłowe dane wejściowe nie trafią do modelu. Nie jestem pewien, jak próbowałeś podejść do rzeczy lub co dokładnie Ci się nie udało, $parsersale oto prosta dyrektywa, która rozwiązuje Twój problem (lub przynajmniej moje zrozumienie problemu):

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

Jak tylko powyższa dyrektywa zostanie zadeklarowana, może być używana w następujący sposób:

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

Podobnie jak w rozwiązaniu zaproponowanym przez @Valentyn Shybanov musimy użyć ng-trimdyrektywy, jeśli chcemy zabronić spacji na początku / końcu wejścia.

Zaleta tego podejścia jest 2-krotna:

  • Nieprawidłowa wartość nie jest propagowana do modelu
  • Korzystając z dyrektywy, łatwo jest dodać tę niestandardową weryfikację do dowolnego wejścia bez ciągłego powielania obserwatorów
pkozlowski.opensource
źródło
1
Jestem pewien, że trudna część była w przypadku modelCtrl.$setViewValue(transformedInput); modelCtrl.$render();Przydatne byłoby łącze do dokumentacji: docs.angularjs.org/api/ng.directive:ngModel.NgModelController Jednym słowem „chronić” moje rozwiązanie jest to, że właściwość zakresu można zmienić nie tylko z widoków i mój sposób to zakryć. Myślę więc, że to, jak można zmodyfikować zakres, zależy od rzeczywistej sytuacji.
Valentyn Shybanov
2
do czego odnosi się „modelCtrl” w twoim przykładzie?
GS do
4
Skąd masz inputValue?
Dofs
2
@GSto modelCtrljest kontrolerem wymaganym przez dyrektywę. ( require 'ngModel')
Nate-Wilkins
7
Kursor przeskakuje na koniec pola tekstowego za każdym razem, gdy wpiszesz nieprawidłowy znak, spróbuj wpisać „świat” i zmodyfikuj go na „Świat HeLLo”!
Hafez Divandari
23

Rozwiązaniem tego problemu mogłoby być zastosowanie filtrów po stronie sterownika:

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

Nie zapomnij zadeklarować $filterjako zależności.

Pierre-Yves Le Dévéhat
źródło
4
Ale potrzebujesz na nim zegarka $ watch, jeśli chcesz, aby był poprawnie aktualizowany.
Pan Mikkél
jest to wykonywane tylko raz. a dodanie do zegarka nie jest dobrym rozwiązaniem, ponieważ nawet na początku pozwala modelowi stać się nieważnym - poprawnym rozwiązaniem jest dodanie do parserów $ modelu.
icfantv
4
Nie musisz podobać się mojej odpowiedzi, ale to nie znaczy, że jest zła. Sprawdź swoje ego, zanim zagłosujesz.
icfantv
6

Jeśli używasz pola wejściowego tylko do odczytu, możesz użyć wartości-ng z filtrem.

na przykład:

ng-value="price | number:8"
Edward D. Wilson
źródło
4

Użyj dyrektywy, która dodaje zarówno do kolekcji $ formatters, jak i $ parsers, aby upewnić się, że transformacja jest wykonywana w obu kierunkach.

Zobacz tę inną odpowiedź, aby uzyskać więcej informacji, w tym link do jsfiddle.

Scott Munro
źródło
3

Miałem podobny problem i korzystałem

ng-change="handler(objectInScope)" 

w moim programie obsługi wywołuję metodę objectInScope, aby poprawnie się zmodyfikować (zgrubne dane wejściowe). W kontrolerze gdzieś to zainicjowałem

$scope.objectInScope = myObject; 

Wiem, że to nie używa żadnych wymyślnych filtrów ani obserwatorów ... ale jest proste i działa świetnie. Jedyną wadą tego rozwiązania jest to, że objectInScope jest wysyłany w wywołaniu do programu obsługi ...

wojjas
źródło
1

Jeśli wykonujesz złożone, asynchroniczne sprawdzanie poprawności danych wejściowych, warto wyodrębnić ng-modeljeden poziom w górę jako część klasy niestandardowej z własnymi metodami walidacji.

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>

kod

(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
źródło
0

Możesz tego spróbować

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

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

});
Nikhil Mahirrao
źródło