วิธีการดูอาร์เรย์ในเชิงลึก


320

มีอาร์เรย์ของวัตถุในขอบเขตของฉันฉันต้องการดูค่าทั้งหมดของแต่ละวัตถุ

นี่คือรหัสของฉัน:

function TodoCtrl($scope) {
  $scope.columns = [
      { field:'title', displayName: 'TITLE'},
      { field: 'content', displayName: 'CONTENT' }
  ];
   $scope.$watch('columns', function(newVal) {
       alert('columns changed');
   });
}

แต่เมื่อฉันแก้ไขค่าเช่นฉันเปลี่ยนTITLEเป็นTITLE2ค่าที่alert('columns changed')ไม่เคยปรากฏ

วิธีการเฝ้าดูวัตถุในอาเรย์อย่างล้ำลึก

มีการสาธิตสด: http://jsfiddle.net/SYx9b/

คำตอบ:


529

คุณสามารถตั้งค่าอาร์กิวเมนต์ที่ 3 $watchเป็นtrue:

$scope.$watch('data', function (newVal, oldVal) { /*...*/ }, true);

ดูhttps://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch

ตั้งแต่ Angular 1.1.x คุณยังสามารถใช้ $ watchCollection เพื่อรับชมตื้น (เพียงแค่ "ระดับแรก" ของ) คอลเลกชัน

$scope.$watchCollection('data', function (newVal, oldVal) { /*...*/ });

ดูhttps://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watchCollection


2
ทำไมคุณกำลังใช้angular.equalsเมื่ออาร์กิวเมนต์ที่สามใช้ค่าบูลีน ?
JayQuerie.com

ขอบคุณ Trevor การอ่านเอกสารผิด ฉันได้อัปเดตรหัสด้านบนและอัปเดตรหัสเพื่อให้ตรงกัน
Piran

36
ใน 1.1.x ตอนนี้คุณมี$watchCollection
Jonathan Rowny

39
$watchCollectionจะดู "ระดับแรก" ของอาเรย์หรือวัตถุอย่างที่ฉันเข้าใจ คำตอบข้างต้นนั้นถูกต้องหากคุณจำเป็นต้องดูลึกกว่านั้น bennadel.com/blog/…
Blazemonger

1
คำตอบที่ดี ... จะได้ให้คุณมากกว่าหนึ่งขึ้นลงคะแนนถ้าผม ... :) ขอบคุณ
Jony-Y

50

มีผลกระทบด้านประสิทธิภาพต่อการดำน้ำลึกวัตถุในนาฬิกา $ ของคุณ บางครั้ง (ตัวอย่างเช่นเมื่อการเปลี่ยนแปลงเป็นเพียงการพุชและป็อป) คุณอาจต้องการ $ ดูค่าที่คำนวณได้ง่ายเช่น array.length


1
ควรมีคะแนนมากกว่านี้ การดูลึกมีราคาแพง ฉันเข้าใจว่า OP กำลังมองหาการรับชมที่ลึกล้ำ แต่ผู้คนอาจมาที่นี่แค่อยากรู้ว่าตัวเองเปลี่ยนไปไหม การดูความยาวนั้นเร็วกว่ามากในกรณีนั้น
Scott Silvi

42
นี่ควรเป็นความเห็นไม่ใช่คำตอบ
Blazemonger

1
ปัญหาด้านประสิทธิภาพจะลดลงโดยใช้ $ watchCollection ตามที่กล่าวถึงโดย @Blazemonger ความคิดเห็น ( stackoverflow.com/questions/14712089#comment32440226_14713978 )
Sean the Bean

คำแนะนำที่เรียบร้อย เกี่ยวกับความคิดเห็นนี้ไม่ใช่คำตอบในความคิดของฉันมันจะดีกว่าที่จะอธิบายรายละเอียดข้อเสนอแนะเพื่อตอบสนองเกณฑ์การยอมรับสำหรับคำตอบ ฉันคิดว่าด้วยวิธีนี้วิธีการแก้ปัญหาที่สง่างามสำหรับคำถามอาจทำได้
Amy Pellegrini

43

หากคุณจะดูอาเรย์เดียวคุณสามารถใช้โค้ดนี้ได้:

$scope.$watch('columns', function() {
  // some value in the array has changed 
}, true); // watching properties

ตัวอย่าง

แต่จะไม่ทำงานกับหลาย ๆ อาร์เรย์:

$scope.$watch('columns + ANOTHER_ARRAY', function() {
  // will never be called when things change in columns or ANOTHER_ARRAY
}, true);

ตัวอย่าง

เพื่อจัดการสถานการณ์นี้ฉันมักจะแปลงหลายอาร์เรย์ที่ฉันต้องการดูเป็น JSON:

$scope.$watch(function() { 
  return angular.toJson([$scope.columns, $scope.ANOTHER_ARRAY, ... ]); 
},
function() {
  // some value in some array has changed
}

ตัวอย่าง

ตามที่ @ jssebastian ชี้ให้เห็นในความคิดเห็นJSON.stringifyอาจจะดีกว่าangular.toJsonเพราะสามารถจัดการกับสมาชิกที่เริ่มต้นด้วย '$' และกรณีอื่น ๆ ที่เป็นไปได้เช่นกัน


ฉันยังพบว่ามีพารามิเตอร์ตัวที่ 3 $watchอยู่สามารถทำเช่นเดียวกันได้หรือไม่ Pass true as a third argument to watch an object's properties too.ดู: cheatography.com/proloser/cheat-sheets/angularjs
Freewind

@Freewind ถ้ามีเคยมีกรณีที่คุณจะต้องดูอาร์เรย์หลายมันจะล้มเหลวที่เห็นนี่ แต่ใช่ว่าจะใช้งานได้ & ให้ฟังก์ชันการทำงานเดียวกับที่ใช้angular.toJsonกับอาเรย์เดี่ยว
JayQuerie.com

2
เพียงระวังว่า angular.toJson ดูเหมือนจะไม่รวมถึงสมาชิกที่ขึ้นต้นด้วย '$': angular.toJson ({"$ hello": "world"}) เป็นเพียง "{}" ฉันใช้ JSON.stringify () เป็นทางเลือก
jssebastian

@ jssebastian ขอบคุณ - ฉันได้อัปเดตคำตอบเพื่อรวมข้อมูลนั้นแล้ว
JayQuerie.com

1
เราจะรู้ได้อย่างไรว่าทรัพย์สินใดที่มีการเปลี่ยนแปลง?
Ashwin

21

เป็นที่น่าสังเกตว่าใน Angular 1.1.x ขึ้นไปคุณสามารถใช้$ watchCollectionแทนที่จะเป็น $ watch แม้ว่า $ watchCollection จะปรากฏขึ้นเพื่อสร้างนาฬิกาแบบตื้นดังนั้นมันจะไม่ทำงานกับอาร์เรย์ของวัตถุอย่างที่คุณคาดหวัง มันสามารถตรวจจับการเพิ่มและการลบอาร์เรย์ แต่ไม่ใช่คุณสมบัติของวัตถุภายในอาร์เรย์


5
$ watchCollection เท่านั้นที่ดูตื้น ๆ ดังนั้นเมื่อฉันเข้าใจแล้วการเปลี่ยนแปลงคุณสมบัติของรายการของอาร์เรย์ (ตามคำถาม) จะไม่ทำให้เกิดเหตุการณ์
Tarnschaf

3
นี่ควรเป็นความเห็นไม่ใช่คำตอบ
Blazemonger

18

นี่คือการเปรียบเทียบ 3 วิธีที่คุณสามารถดูตัวแปรขอบเขตด้วยตัวอย่าง:

$ watch ()ถูกสั่งการโดย:

$scope.myArray = [];
$scope.myArray = null;
$scope.myArray = someOtherArray;

$ watchCollection ()ถูกเรียกใช้โดยทุกอย่างที่อยู่ด้านบนและ:

$scope.myArray.push({}); // add element
$scope.myArray.splice(0, 1); // remove element
$scope.myArray[0] = {}; // assign index to different value

$ watch (... , จริง)ถูกเรียกใช้โดยทุกอย่างด้านบนและ:

$scope.myArray[0].someProperty = "someValue";

เพียงแค่อีกสิ่ง ...

$ watch ()เป็นเพียงหนึ่งเดียวที่ทริกเกอร์เมื่ออาเรย์ถูกแทนที่ด้วยอาเรย์อื่นแม้ว่าอาเรย์นั้นจะมีเนื้อหาที่แน่นอนเหมือนกัน

เช่นที่$watch()จะยิงและ$watchCollection()จะไม่:

$scope.myArray = ["Apples", "Bananas", "Orange" ];

var newArray = [];
newArray.push("Apples");
newArray.push("Bananas");
newArray.push("Orange");

$scope.myArray = newArray;

ด้านล่างนี้คือลิงก์ไปยังตัวอย่าง JSFiddle ที่ใช้ชุดค่าผสมการเฝ้าดูที่แตกต่างกันและข้อความบันทึกการแสดงผลเพื่อระบุว่า "การเฝ้าดู" ที่ถูกเรียกใช้:

http://jsfiddle.net/luisperezphd/2zj9k872/


โดยเฉพาะอย่างยิ่ง especailly ที่จะบันทึก 'อีกสิ่งหนึ่ง'
Andy Ma

12

$ watchCollectionบรรลุสิ่งที่คุณต้องการทำ ด้านล่างนี้เป็นตัวอย่างที่คัดลอกมาจากเว็บไซต์ angularjs http://docs.angularjs.org/api/ng/tyro/$rootScope.Scope ในขณะที่สะดวกประสิทธิภาพการทำงานจะต้องนำมาพิจารณาโดยเฉพาะเมื่อคุณดูชุดใหญ่

  $scope.names = ['igor', 'matias', 'misko', 'james'];
  $scope.dataCount = 4;

  $scope.$watchCollection('names', function(newNames, oldNames) {
     $scope.dataCount = newNames.length;
  });

  expect($scope.dataCount).toEqual(4);
  $scope.$digest();

  //still at 4 ... no changes
  expect($scope.dataCount).toEqual(4);

  $scope.names.pop();
  $scope.$digest();

  //now there's been a change
  expect($scope.dataCount).toEqual(3);

14
OP ระบุอาร์เรย์ของวัตถุ ตัวอย่างของคุณทำงานกับอาร์เรย์ของสตริง แต่ $ watchCollection ไม่ทำงานกับอาร์เรย์ของวัตถุ
KevinL

4

โซลูชันนี้ทำงานได้ดีมากสำหรับฉันฉันกำลังทำสิ่งนี้ในแนวทาง:

ขอบเขต $ watch (attrs.testWatch, function () {..... }, จริง);

การใช้งานจริงค่อนข้างดีและตอบสนองต่อการเปลี่ยนแปลงทั้งหมด (เพิ่มลบหรือปรับเปลี่ยนฟิลด์)

นี่คือพลั่วเกอร์ที่ใช้งานได้เพื่อเล่นกับมัน

การดูอาเรย์ในเชิงลึกอย่างลึกซึ้ง JS

ฉันหวังว่าสิ่งนี้จะเป็นประโยชน์สำหรับคุณ หากคุณมีคำถามใด ๆ อย่าลังเลที่จะถามฉันจะพยายามช่วย :)


4

ในกรณีของฉันฉันจำเป็นต้องดูบริการซึ่งมีวัตถุที่อยู่ยังดูโดยตัวควบคุมอื่น ๆ ฉันติดอยู่ในลูปจนกว่าฉันจะเพิ่มพารามิเตอร์ 'ของจริง' ซึ่งดูเหมือนจะเป็นกุญแจสู่ความสำเร็จเมื่อดูวัตถุ

$scope.$watch(function() {
    return LocationService.getAddress();
}, function(address) {
    //handle address object
}, true);

1

การตั้งค่าobjectEqualityพารามิเตอร์ (พารามิเตอร์ที่สาม) ของ$watchฟังก์ชันเป็นวิธีที่ถูกต้องในการดูคุณสมบัติทั้งหมดของอาร์เรย์

$scope.$watch('columns', function(newVal) {
    alert('columns changed');
},true); // <- Right here

Piranตอบคำถามนี้ได้ดีพอและกล่าวถึง$watchCollectionเช่นกัน

รายละเอียดเพิ่มเติม

เหตุผลที่ฉันตอบคำถามที่ตอบแล้วเพราะฉันต้องการชี้ให้เห็นว่าคำตอบของพ่อมดแม่มดไม่ดีและไม่ควรใช้

ปัญหาคือการย่อยไม่เกิดขึ้นทันที พวกเขาต้องรอจนกว่าบล็อกรหัสปัจจุบันจะเสร็จสมบูรณ์ก่อนดำเนินการ ดังนั้นการเฝ้าดูlengthอาเรย์อาจพลาดการเปลี่ยนแปลงที่สำคัญที่$watchCollectionจะเกิดขึ้น

สมมติว่าการกำหนดค่านี้:

$scope.testArray = [
    {val:1},
    {val:2}
];

$scope.$watch('testArray.length', function(newLength, oldLength) {
    console.log('length changed: ', oldLength, ' -> ', newLength);
});

$scope.$watchCollection('testArray', function(newArray) {
    console.log('testArray changed');
});

เมื่อมองแวบแรกดูเหมือนว่าสิ่งเหล่านี้จะเกิดขึ้นพร้อมกันเช่นในกรณีนี้:

function pushToArray() {
    $scope.testArray.push({val:3});
}
pushToArray();

// Console output
// length changed: 2 -> 3
// testArray changed

ใช้งานได้ดี แต่ให้พิจารณาสิ่งนี้:

function spliceArray() {
    // Starting at index 1, remove 1 item, then push {val: 3}.
    $testArray.splice(1, 1, {val: 3});
}
spliceArray();

// Console output
// testArray changed

ขอให้สังเกตว่าความยาวที่เกิดขึ้นจะเท่ากันแม้ว่าอาเรย์จะมีองค์ประกอบใหม่และทำให้องค์ประกอบหายไปดังนั้นในขณะที่การเฝ้าดูที่$watchเกี่ยวข้องนั้นlengthยังไม่เปลี่ยนแปลง $watchCollectionหยิบขึ้นมาบนมันแม้ว่า

function pushPopArray() {
    $testArray.push({val: 3});
    $testArray.pop();
}
pushPopArray();

// Console output
// testArray change

ผลลัพธ์เดียวกันนั้นเกิดขึ้นเมื่อกดและป๊อปอัพในบล็อกเดียวกัน

ข้อสรุป

ในการดูทุกคุณสมบัติในอาเรย์ใช้ a $watchบนอาเรย์ด้วยตัวเองพร้อมกับพารามิเตอร์ที่สาม (objectEquality) รวมอยู่และตั้งค่าเป็นจริง ใช่มันแพง แต่บางครั้งก็จำเป็น

หากต้องการดูเมื่อวัตถุป้อน / $watchCollectionออกจากอาร์เรย์การใช้งาน

อย่าใช้$watchกับlengthคุณสมบัติของอาร์เรย์ เกือบจะไม่มีเหตุผลที่ดีที่ฉันสามารถคิดทำ


0

$scope.changePass = function(data){
    
    if(data.txtNewConfirmPassword !== data.txtNewPassword){
        $scope.confirmStatus = true;
    }else{
        $scope.confirmStatus = false;
    }
};
  <form class="list" name="myForm">
      <label class="item item-input">        
        <input type="password" placeholder="ใส่รหัสผ่านปัจจุบัน" ng-model="data.txtCurrentPassword" maxlength="5" required>
      </label>
      <label class="item item-input">
        <input type="password" placeholder="ใส่รหัสผ่านใหม่" ng-model="data.txtNewPassword" maxlength="5" ng-minlength="5" name="checknawPassword" ng-change="changePass(data)" required>
      </label>
      <label class="item item-input">
        <input type="password" placeholder="ใส่รหัสผ่านใหม่ให้ตรงกัน" ng-model="data.txtNewConfirmPassword" maxlength="5" ng-minlength="5" name="checkConfirmPassword" ng-change="changePass(data)" required>
      </label>      
       <div class="spacer" style="width: 300px; height: 5px;"></div> 
      <span style="color:red" ng-show="myForm.checknawPassword.$error.minlength || myForm.checkConfirmPassword.$error.minlength">รหัสผ่านต้องมีจำนวน 5 หลัก</span><br>
      <span ng-show="confirmStatus" style="color:red">รหัสผ่านใหม่ไม่ตรงกัน</span>
      <br>
      <button class="button button-positive  button-block" ng-click="saveChangePass(data)" ng-disabled="myForm.$invalid || confirmStatus">เปลี่ยน</button>
    </form>

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.