AngularJS - Dalam arahan yang mengubah nilai model, mengapa saya harus memanggil $render?

Saya membuat arahan yang dirancang untuk dilampirkan ke elemen menggunakan arahan ngModel. Jika nilai model cocok dengan sesuatu, maka nilainya harus ditetapkan ke nilai sebelumnya. Dalam contoh saya, saya mencari "foo", dan mengaturnya kembali ke sebelumnya jika itu yang diketik.

Tes unit saya lulus dengan baik karena mereka hanya melihat nilai model. Namun dalam praktiknya DOM tidak diperbarui ketika "put back" terpicu. Tebakan terbaik kami di sini adalah pengaturan old == new mencegah terjadinya cek kotor. Saya menelusuri metode $setViewValue dan tampaknya melakukan apa yang seharusnya. Namun itu tidak akan memperbarui DOM (dan apa yang Anda lihat di browser) sampai saya secara eksplisit memanggil ngModel.$render() setelah menetapkan nilai baru. Ini berfungsi dengan baik, tapi saya hanya ingin melihat apakah ada cara yang lebih tepat untuk melakukan ini.

Kodenya ada di bawah, ini biola yang sama.

angular.module('myDirective', [])
    .directive('myDirective', function () {
    return {
        restrict: 'A',
        terminal: true,
        require: "?ngModel",
        link: function (scope, element, attrs, ngModel) {
            scope.$watch(attrs.ngModel, function (newValue, oldValue) {
                //ngModel.$setViewValue(newValue + "!");   

                if (newValue == "foo")
                {
                    ngModel.$setViewValue(oldValue);   
                    /* 
                        I Need this render call in order to update the input box; is that OK?
                        My best guess is that setting new = old prevents a dirty check which would trigger $render()
                    */
                    ngModel.$render();
                }
            });
        }
    };
});

function x($scope) {
    $scope.test = 'value here';
}

person David Peters    schedule 03.10.2013    source sumber


Jawaban (1)


Tebakan terbaik kami di sini adalah pengaturan old == new mencegah terjadinya cek kotor

Listener pengamat hanya dipanggil ketika nilai ekspresi yang didengarkannya berubah. Namun karena Anda mengubah model kembali ke nilai sebelumnya, model tersebut tidak akan dipanggil lagi karena sepertinya nilainya tidak berubah sama sekali. Namun hati-hati: mengubah nilai properti di dalam pengamat yang memantau properti yang sama dapat menyebabkan perulangan tak terbatas.

Namun itu tidak akan memperbarui DOM (dan apa yang Anda lihat di browser) sampai saya secara eksplisit memanggil ngModel.$render() setelah menetapkan nilai baru.

Itu benar. $setViewValue menyetel nilai model seolah-olah diperbarui oleh tampilan, namun Anda perlu memanggil $render untuk merender tampilan secara efektif berdasarkan nilai model (baru). Lihat diskusi ini untuk informasi lebih lanjut.

Terakhir, menurut saya Anda harus menangani masalah Anda dengan cara yang berbeda. Anda dapat menggunakan properti $parsers dari NgModelController untuk memvalidasi input pengguna, alih-alih menggunakan pengamat:

link: function (scope, element, attrs, ngModel) {
  if (!ngModel) return;

  ngModel.$parsers.unshift(function(viewValue) {
    if(viewValue === 'foo') {                 
      var currentValue = ngModel.$modelValue;
      ngModel.$setViewValue(currentValue);
      ngModel.$render(); 
      return currentValue;
    }
    else 
      return viewValue;
  });
}

Saya mengubah skrip jsFiddle Anda untuk menggunakan kode di atas.

angular.module('myDirective', [])
.directive('myDirective', function () {
  return {
    restrict: 'A',
    terminal: true,
    require: "?ngModel",
    link: function (scope, element, attrs, ngModel) {
      if (!ngModel) return;

      ngModel.$parsers.unshift(function(viewValue) {
        if(viewValue === 'foo') {                 
          var currentValue = ngModel.$modelValue;
          ngModel.$setViewValue(currentValue);
          ngModel.$render(); 
          return currentValue;
        }
        else 
          return viewValue;
      });
    }
  };
});

function x($scope) {
  $scope.test = 'value here';
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<h1>Foo Fighter</h1>
I hate "foo", just try and type it in the box.
<div ng-app="myDirective" ng-controller="x">
  <input type="text" ng-model="test" my-directive>
  <br />
  model: {{test}}
</div>

person Michael Benford    schedule 03.10.2013
comment
Terima kasih, menambahkan $parser (atau $formatter?) jauh lebih masuk akal daripada apa yang saya lakukan. Saya mengalami masalah saat menguji unit ini; apakah saya harus memanggil metode Angular tambahan untuk memulai proses parse? Saat ini ketika pengujian unit saya memanipulasi nilai model, pengendali parser baru tidak diaktifkan. - person David Peters; 04.10.2013
comment
$parser digunakan untuk memvalidasi apa yang berasal dari tampilan dan $formatter digunakan untuk memformat model sebelum dirender. Jika Anda ingin mencegah seseorang menambahkan foo langsung ke model, penggunaan parser tidak akan berhasil. - person Michael Benford; 04.10.2013
comment
Tentang pengujian, pada dasarnya Anda harus memicu peristiwa input pada kolom input tempat direktif ng-model diterapkan. Itu cukup bagi Angular untuk menjalankan kode Anda. Jika Anda memerlukan contoh, buat pertanyaan lain dan saya akan menjawabnya untuk Anda. Saya khawatir komentarnya akan berantakan jika saya taruh di sini. - person Michael Benford; 04.10.2013
comment
Temukan answer/fiddle Anda yang lain tentang topik tersebut, berfungsi dengan baik. Terima kasih lagi! - person David Peters; 04.10.2013
comment
Oh, aku tidak ingat pertanyaan itu. Senang Anda menemukannya. Mungkin Anda tertarik untuk melihat the tes Saya telah menulis untuk arahan yang saya bicarakan di posting itu. - person Michael Benford; 04.10.2013