AngularJS - ในคำสั่งที่เปลี่ยนค่าโมเดล ทำไมฉันต้องเรียก $render?

ฉันสร้างคำสั่งที่ออกแบบมาเพื่อแนบกับองค์ประกอบโดยใช้คำสั่ง ngModel หากค่าของโมเดลตรงกับค่าบางอย่าง ค่าควรตั้งค่าเป็นค่าก่อนหน้า ในตัวอย่างของฉัน ฉันกำลังมองหา "foo" และตั้งค่ากลับเป็นค่าก่อนหน้าหากนั่นคือสิ่งที่พิมพ์ไว้

การทดสอบหน่วยของฉันผ่านได้ดีในเรื่องนี้เพราะพวกเขาดูเฉพาะค่าโมเดลเท่านั้น อย่างไรก็ตาม ในทางปฏิบัติ DOM จะไม่อัปเดตเมื่อมีการทริกเกอร์ "put back" การคาดเดาที่ดีที่สุดของเราที่นี่คือการตั้งค่าเก่า == ใหม่จะป้องกันไม่ให้เช็คสกปรกเกิดขึ้น ฉันทำตามขั้นตอน $setViewValue และดูเหมือนว่าจะทำสิ่งที่ควรทำ อย่างไรก็ตาม มันจะไม่อัปเดต DOM (และสิ่งที่คุณเห็นในเบราว์เซอร์) จนกว่าฉันจะเรียก ngModel.$render() อย่างชัดเจนหลังจากตั้งค่าใหม่ มันใช้งานได้ดี แต่ฉันแค่อยากจะดูว่ามีวิธีที่เหมาะสมกว่านี้หรือไม่

โค้ดอยู่ด้านล่าง นี่คือซอที่มีเหมือนกัน

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 แหล่งที่มา


คำตอบ (1)


การคาดเดาที่ดีที่สุดของเราที่นี่คือการตั้งค่าเก่า == ใหม่จะป้องกันไม่ให้เช็คสกปรกเกิดขึ้น

Listener ของ Watcher จะถูกเรียกเมื่อค่าของนิพจน์ที่ฟังมีการเปลี่ยนแปลงเท่านั้น แต่เนื่องจากคุณเปลี่ยนโมเดลกลับไปเป็นค่าก่อนหน้า มันจะไม่ถูกเรียกอีกเพราะเหมือนกับว่าค่าไม่มีการเปลี่ยนแปลงเลย แต่ต้องระวัง: การเปลี่ยนแปลงค่าของคุณสมบัติภายในตัวเฝ้าดูที่มอนิเตอร์ว่าคุณสมบัติเดียวกันสามารถนำไปสู่การวนซ้ำไม่สิ้นสุด

อย่างไรก็ตาม มันจะไม่อัปเดต DOM (และสิ่งที่คุณเห็นในเบราว์เซอร์) จนกว่าฉันจะเรียก ngModel.$render() อย่างชัดเจนหลังจากตั้งค่าใหม่

ถูกต้อง. $setViewValue ตั้งค่าโมเดลราวกับว่าได้รับการอัปเดตโดยมุมมอง แต่คุณต้องเรียก $render เพื่อเรนเดอร์มุมมองอย่างมีประสิทธิภาพตามค่าโมเดล (ใหม่) ดูการสนทนานี้เพื่อดูข้อมูลเพิ่มเติม

สุดท้ายนี้ ฉันคิดว่าคุณควรแก้ไขปัญหาด้วยวิธีอื่น คุณสามารถใช้คุณสมบัติ $parsers ของ NgModelController เพื่อตรวจสอบอินพุตของผู้ใช้ แทนที่จะใช้ตัวเฝ้าดู:

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

ฉันเปลี่ยน สคริปต์ jsFiddle ของคุณเพื่อใช้โค้ดด้านบน

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
ขอบคุณ การเพิ่ม $parser (หรือ $formatter?) สมเหตุสมผลมากกว่าสิ่งที่ฉันทำอยู่มาก ฉันมีปัญหาในการทดสอบหน่วยนี้ ฉันต้องเรียกใช้เมธอด Angular เพิ่มเติมเพื่อเริ่มกระบวนการแยกวิเคราะห์หรือไม่ ขณะนี้เมื่อการทดสอบหน่วยของฉันจัดการค่าโมเดล ตัวจัดการพาร์เซอร์ใหม่จะไม่เริ่มทำงาน - person David Peters; 04.10.2013
comment
$parser ใช้เพื่อตรวจสอบสิ่งที่มาจากมุมมอง และ $formatter ใช้เพื่อจัดรูปแบบโมเดลก่อนที่จะแสดงผล หากคุณต้องการป้องกันไม่ให้บุคคลอื่นเพิ่ม foo ลงในโมเดลโดยตรง การใช้ parser จะไม่ทำงาน - person Michael Benford; 04.10.2013
comment
เกี่ยวกับการทดสอบ โดยพื้นฐานแล้ว คุณควรทริกเกอร์เหตุการณ์ input ในช่องป้อนข้อมูลที่ใช้คำสั่ง ng-model นั่นก็เพียงพอแล้วสำหรับ Angular ที่จะรันโค้ดของคุณ หากคุณต้องการตัวอย่าง โปรดสร้างคำถามใหม่ แล้วฉันจะตอบให้คุณ ฉันกลัวว่าความคิดเห็นจะเกะกะถ้าฉันใส่ไว้ที่นี่ - person Michael Benford; 04.10.2013
comment
พบ answer/fiddle อื่นๆ ของคุณในหัวข้อนี้ ใช้งานได้ดี ขอบคุณอีกครั้ง! - person David Peters; 04.10.2013
comment
เอ่อ ฉันจำคำถามนั้นไม่ได้ ดีใจที่คุณพบมัน บางทีคุณอาจสนใจที่จะลองดู การทดสอบ ที่ฉันเขียนสำหรับคำสั่งที่ฉันพูดถึงในโพสต์นั้น - person Michael Benford; 04.10.2013