ฉันจะใช้ $ scope $ เฝ้าดูและ $ scope $ นำไปใช้ใน AngularJS ได้อย่างไร


1088

ฉันไม่เข้าใจวิธีการใช้งานและ$scope.$watch $scope.$applyเอกสารทางการไม่เป็นประโยชน์

สิ่งที่ฉันไม่เข้าใจเป็นพิเศษ:

  • พวกเขาเชื่อมต่อกับ DOM หรือไม่?
  • ฉันจะอัปเดตการเปลี่ยนแปลง DOM เป็นโมเดลได้อย่างไร
  • จุดเชื่อมต่อระหว่างพวกเขาคืออะไร?

ฉันพยายามกวดวิชานี้แต่มันต้องใช้ความเข้าใจของ$watchและ$applyให้ได้รับ

ทำอะไร$applyและ$watchทำอย่างไรและฉันจะใช้อย่างเหมาะสมได้อย่างไร

คำตอบ:


1737

คุณต้องระวังเกี่ยวกับการทำงานของ AngularJS เพื่อทำความเข้าใจ

รอบย่อยและขอบเขต $

แรกและสำคัญที่สุด AngularJS กำหนดแนวคิดของที่เรียกว่าย่อยวงจร รอบนี้ถือได้ว่าเป็นวงในระหว่างที่ AngularJS ตรวจสอบว่ามีการเปลี่ยนแปลงใด ๆ กับตัวแปรทั้งหมดที่ดูโดย$scopes ทั้งหมด ดังนั้นหากคุณได้$scope.myVarกำหนดไว้ในคอนโทรลเลอร์ของคุณและตัวแปรนี้ถูกทำเครื่องหมายเพื่อรับการเฝ้าดูคุณจะบอก AngularJS ให้ติดตามการเปลี่ยนแปลงmyVarในแต่ละรอบของลูป

คำถามติดตามอย่างเป็นธรรมชาติจะเป็น: ทุกสิ่งที่แนบมากับ$scopeการเฝ้าดู? โชคดีที่ไม่มี หากคุณจะเฝ้าสังเกตการเปลี่ยนแปลงของวัตถุทุกชิ้นในวงของคุณ$scopeอย่างรวดเร็ววงย่อยจะใช้เวลานานในการประเมินและคุณจะพบปัญหาประสิทธิภาพการทำงานอย่างรวดเร็ว นั่นคือเหตุผลที่ทีม AngularJS ให้เราสองวิธีในการประกาศ$scopeตัวแปรบางอย่างที่ถูกเฝ้าดู (อ่านด้านล่าง)

$ watch ช่วยฟังการเปลี่ยนแปลง $ scope

การประกาศ$scopeตัวแปรมีสองวิธี

  1. โดยใช้ในแม่แบบของคุณผ่านการแสดงออก <span>{{myVar}}</span>
  2. โดยการเพิ่มด้วยตนเองผ่าน$watchบริการ

โฆษณา 1) นี่เป็นสถานการณ์ที่พบบ่อยที่สุดและฉันแน่ใจว่าคุณเคยเห็นมาก่อน แต่คุณไม่รู้ว่าสิ่งนี้ได้สร้างนาฬิกาในพื้นหลัง ใช่มันมี! การใช้คำสั่ง AngularJS (เช่นng-repeat) ยังสามารถสร้างนาฬิกาโดยนัยได้

โฆษณา 2) นี้เป็นวิธีที่คุณสร้างของคุณเองนาฬิกา $watchบริการช่วยให้คุณเรียกใช้รหัสบางอย่างเมื่อค่าบางอย่างที่แนบกับการ$scopeเปลี่ยนแปลง มันไม่ค่อยได้ใช้ แต่บางครั้งก็มีประโยชน์ ตัวอย่างเช่นหากคุณต้องการรันบางรหัสในแต่ละครั้งที่มีการเปลี่ยนแปลง 'myVar' คุณสามารถทำสิ่งต่อไปนี้:

function MyController($scope) {

    $scope.myVar = 1;

    $scope.$watch('myVar', function() {
        alert('hey, myVar has changed!');
    });

    $scope.buttonClicked = function() {
        $scope.myVar = 2; // This will trigger $watch expression to kick in
    };
}

$ Apply ช่วยให้สามารถรวมการเปลี่ยนแปลงกับวงจรย่อย

คุณสามารถคิดของฟังก์ชั่นเป็นของกลไกการรวม$apply คุณเห็นทุกครั้งที่คุณเปลี่ยนตัวแปรเฝ้าดู$scopeบางตัวที่ติดอยู่กับวัตถุโดยตรง AngularJS จะรู้ว่ามีการเปลี่ยนแปลงเกิดขึ้น นี่เป็นเพราะ AngularJS รู้อยู่แล้วว่าจะตรวจสอบการเปลี่ยนแปลงเหล่านั้น ดังนั้นหากเกิดขึ้นในโค้ดที่จัดการโดยเฟรมเวิร์กการแยกย่อยจะดำเนินต่อไป

อย่างไรก็ตามบางครั้งคุณต้องการเปลี่ยนค่าบางอย่างนอกโลก AngularJSและดูการเปลี่ยนแปลงที่เผยแพร่ตามปกติ พิจารณาสิ่งนี้ - คุณมี$scope.myVarค่าที่จะแก้ไขภายใน$.ajax()ตัวจัดการของ jQuery สิ่งนี้จะเกิดขึ้นในอนาคต AngularJS ไม่สามารถรอให้สิ่งนี้เกิดขึ้นได้เนื่องจากยังไม่ได้รับคำสั่งให้รอ jQuery

เพื่อแก้ไขปัญหานี้$applyได้รับการแนะนำ ช่วยให้คุณเริ่มรอบการย่อยได้อย่างชัดเจน อย่างไรก็ตามคุณควรใช้สิ่งนี้เพื่อโอนย้ายข้อมูลบางอย่างไปยัง AngularJS (รวมกับกรอบงานอื่น ๆ ) แต่ไม่ควรใช้วิธีนี้รวมกับรหัส AngularJS ปกติเนื่องจาก AngularJS จะเกิดข้อผิดพลาด

ทั้งหมดนี้เกี่ยวข้องกับ DOM อย่างไร

ทีนี้คุณควรติดตามการสอนจริง ๆ อีกครั้งตอนนี้คุณก็รู้ทั้งหมดแล้ว รอบการย่อยจะทำให้แน่ใจว่า UI และรหัส JavaScript นั้นยังคงได้รับการซิงโครไนซ์โดยการประเมินผู้เฝ้าดูทุกคนที่แนบมากับทุกคน$scopeตราบใดที่ไม่มีการเปลี่ยนแปลง หากไม่มีการเปลี่ยนแปลงเกิดขึ้นในลูปการแยกย่อยจะถือว่าเสร็จสิ้น

คุณสามารถแนบวัตถุกับ$scopeวัตถุอย่างชัดเจนในตัวควบคุมหรือโดยการประกาศพวกเขาใน{{expression}}รูปแบบโดยตรงในมุมมอง

ฉันหวังว่าจะช่วยชี้แจงความรู้พื้นฐานเกี่ยวกับสิ่งนี้

อ่านเพิ่มเติม:


57
"ตรวจสอบเชิงมุมว่ามีการเปลี่ยนแปลงใด ๆ กับตัวแปรทั้งหมดที่แนบกับ $ scopes ทั้งหมด" - ฉันไม่คิดว่ามันค่อนข้างถูกต้อง ฉันเชื่อว่า Angular เท่านั้น (สกปรก) ตรวจสอบคุณสมบัติ $ scope ที่มีการตั้งค่า $ watches (โปรดทราบว่าการใช้ {{}} ในมุมมองจะสร้าง $ watch โดยอัตโนมัติ) ดูเพิ่มเติมส่วน "ขอบเขต $ นาฬิกาข้อควรพิจารณาผลการปฏิบัติงาน" บนหน้าขอบเขต
Mark Rajcok

5
นั่นอาจเป็นกรณี ฉันจะพยายามหาเวลาอ่านเพิ่มเติมและแก้ไขคำตอบของฉัน
ŁukaszBachman

15
@ MarkRajcok คุณพูดถูก ฉันแก้ไขคำตอบของฉันและชี้ไปที่บทความที่แสดงวิธีการนำไปใช้
ŁukaszBachman

3
แล้วการใช้สิ่งนี้ล่ะ? (วิธีการควบคุมเป็น ")
ลีอันโดร

2
การใช้ "การควบคุมเป็น" ไม่ควรมีผลกระทบต่อข้อมูลข้างต้น การใช้ this.myVar ทำให้ myVar อยู่ในขอบเขต
Marcus Rådell

161

ใน AngularJS เราอัปเดตโมเดลของเราและมุมมอง / เทมเพลตของเราอัปเดต DOM "อัตโนมัติ" (ผ่านคำสั่งในตัวหรือกำหนดเอง)

$ Apply และ $ watch ทั้งคู่เป็นวิธีการ Scope ไม่เกี่ยวข้องกับ DOM

แนวคิดหน้า (ส่วน "Runtime") มีคำอธิบายที่ดีงามของ $ ย่อยห่วง $ ใช้คิว $ และ $ evalAsync รายการนาฬิกา นี่คือภาพที่มาพร้อมกับข้อความ:

$ แยกย่อย

สิ่งที่รหัสมีการเข้าถึงขอบเขต - ปกติตัวควบคุมและคำสั่ง (ฟังก์ชั่นการเชื่อมโยงและ / หรือตัวควบคุม) - สามารถตั้งค่า " watchExpression " ที่ AngularJS จะประเมินเทียบกับขอบเขตนั้น การประเมินนี้เกิดขึ้นเมื่อใดก็ตามที่ AngularJS เข้าสู่ $ digest loop (โดยเฉพาะวง "$ watch list") คุณสามารถดูคุณสมบัติขอบเขตแต่ละตัวคุณสามารถกำหนดฟังก์ชั่นเพื่อดูคุณสมบัติสองอย่างพร้อมกันคุณสามารถดูความยาวของอาร์เรย์ ฯลฯ

เมื่อสิ่งต่าง ๆ เกิดขึ้น "ข้างใน AngularJS" - เช่นคุณพิมพ์ลงในกล่องข้อความที่เปิดใช้งานฐานข้อมูลแบบสองทางของ AngularJS (เช่นใช้ ng-model), ไฟโทรกลับ http http และอื่น ๆ - $ Apply ได้ถูกเรียกใช้แล้ว อยู่ภายในสี่เหลี่ยม "AngularJS" ในรูปด้านบน การแสดงออกของนาฬิกาทั้งหมดจะถูกประเมิน (อาจมากกว่าหนึ่งครั้ง - จนกว่าจะไม่ตรวจพบการเปลี่ยนแปลงเพิ่มเติม)

เมื่อสิ่งต่าง ๆ เกิดขึ้น "นอก AngularJS" - เช่นคุณใช้ bind () ในคำสั่งและจากนั้นเหตุการณ์จะเกิดไฟไหม้ซึ่งส่งผลให้เกิดการโทรกลับของคุณถูกเรียกกลับมาหรือ jQuery ที่ลงทะเบียนไว้โทรกลับ - เรายังคงอยู่ในสี่เหลี่ยมผืนผ้า หากรหัสโทรกลับแก้ไขอะไรที่ $ watch ใด ๆ กำลังรับชมอยู่ให้เรียกใช้ $ เพื่อเข้าสู่สี่เหลี่ยม AngularJS ทำให้ลูป $ แยกย่อยทำงานและ AngularJS จะสังเกตเห็นการเปลี่ยนแปลงและทำเวทมนต์


5
ฉันเข้าใจความคิดสิ่งที่ฉันไม่เข้าใจคือวิธีการถ่ายโอนข้อมูลจริง ฉันมีรูปแบบซึ่งเป็นวัตถุที่มีข้อมูลจำนวนมากฉันใช้บางอย่างเพื่อจัดการ DOM แล้วบางส่วนก็เปลี่ยนไป ฉันจะวางข้อมูลที่เปลี่ยนแปลงในตำแหน่งที่ถูกต้องในโมเดลได้อย่างไร ในตัวอย่างที่ฉันใช้เขาใช้การจัดการและท้ายที่สุดใช้ง่ายscope.$apply(scope.model)ฉันไม่เข้าใจว่าข้อมูลใดที่ถูกถ่ายโอนและวิธีการถ่ายโอนไปยังตำแหน่งที่ถูกต้องในแบบจำลองอย่างไร
ilyo

6
ไม่มีการถ่ายโอนข้อมูลขลัง โดยปกติแล้วกับแอปพลิเคชัน Angular คุณควรเปลี่ยนรุ่นของ Angular ซึ่งจะเป็นการปรับปรุงการดู / DOM หากคุณอัปเดต DOM นอก Angular คุณจะต้องอัปเดตโมเดลด้วยตนเอง scope.$apply(scope.model)จะประเมินเพียงscope.modelว่าเป็นนิพจน์เชิงมุมและจากนั้นป้อน $ loop loop ในบทความที่คุณอ้างถึงอาจscope.$apply()จะเพียงพอเนื่องจากโมเดลนั้นเป็น $ watch'ed แล้ว ฟังก์ชั่น stop () กำลังอัปเดตโมเดล (ฉันเชื่อว่า toUpdate เป็นการอ้างอิงถึง scope.model) จากนั้นจะเรียกใช้ $
Mark Rajcok

ดูเหมือนว่าเอกสาร AngularJS ได้เลื่อนออกจากคำตอบนี้แล้ว (ลิงก์แรกไม่มี "รันไทม์" หรือ$watchบนหน้าและลิงค์ที่สองเสีย - ตอนนี้ไม่ว่าอย่างไรก็ตาม) อย่างเจ็บปวดเวอร์ชันเก็บถาวรไม่แคชอะไรก็ตามที่กระบวนการซิงค์สร้างเนื้อหา
ruffin

52

AngularJS ขยายนี้เหตุการณ์วง , AngularJS contextการสร้างสิ่งที่เรียกว่า

$ นาฬิกา ()

ทุกครั้งที่คุณผูกบางสิ่งบางอย่างใน UI คุณใส่$watchใน$watchรายการ

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

ที่นี่เรามี$scope.userซึ่งถูกผูกไว้กับอินพุตแรกและเรามี$scope.passซึ่งถูกผูกไว้กับที่สอง การทำเช่นนี้เราเพิ่มสอง$watchES ไปยัง$watchรายการ

เมื่อโหลดเทมเพลตของเราAKA ในขั้นตอนการเชื่อมโยงคอมไพเลอร์จะมองหาทุกคำสั่งและสร้าง$watches ทั้งหมดที่จำเป็น

AngularJS ให้$watch, และ$watchcollection $watch(true)ด้านล่างเป็นแผนภาพเรียบร้อยอธิบายทั้งสามนำมาจากนักดูในเชิงลึก

ป้อนคำอธิบายภาพที่นี่

angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout) {
  $scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}];

  $scope.$watch("users", function() {
    console.log("**** reference checkers $watch ****")
  });

  $scope.$watchCollection("users", function() {
    console.log("**** Collection  checkers $watchCollection ****")
  });

  $scope.$watch("users", function() {
    console.log("**** equality checkers with $watch(true) ****")
  }, true);

  $timeout(function(){
     console.log("Triggers All ")
     $scope.users = [];
     $scope.$digest();

     console.log("Triggers $watchCollection and $watch(true)")
     $scope.users.push({ name: 'Thalaivar'});
     $scope.$digest();

     console.log("Triggers $watch(true)")
     $scope.users[0].name = 'Superstar';
     $scope.$digest();
  });
}

http://jsfiddle.net/2Lyn0Lkb/

$digest ห่วง

เมื่อเบราว์เซอร์ได้รับเหตุการณ์ที่สามารถจัดการโดยบริบท AngularJS $digestลูปจะเริ่มทำงาน ห่วงนี้ทำจากสองวงเล็ก ๆ หนึ่งในการประมวลผล$evalAsyncคิวและอื่น ๆ $watch listหนึ่งกระบวนการ $digestประสงค์ห่วงผ่านรายการของ$watchที่เรามี

app.controller('MainCtrl', function() {
  $scope.name = "vinoth";

  $scope.changeFoo = function() {
      $scope.name = "Thalaivar";
  }
});

{{ name }}
<button ng-click="changeFoo()">Change the name</button>

ที่นี่เรามีเพียงอันเดียว$watchเนื่องจาก ng-click ไม่ได้สร้างนาฬิกาใด ๆ

เรากดปุ่ม

  1. เบราว์เซอร์ได้รับเหตุการณ์ซึ่งจะเข้าสู่บริบท AngularJS
  2. การ$digestวนซ้ำจะทำงานและจะขอให้ทุก ๆ คอยดูการเปลี่ยนแปลง
  3. เนื่องจากสิ่ง$watchที่เฝ้าดูการเปลี่ยนแปลงใน $ scope.name รายงานการเปลี่ยนแปลงจึงจะบังคับให้$digestวนซ้ำอีกครั้ง
  4. วนซ้ำใหม่ไม่มีอะไรรายงาน
  5. เบราว์เซอร์ได้รับการควบคุมกลับมาและมันจะอัพเดท DOM ที่สะท้อนถึงค่าใหม่ของ $ scope.name
  6. สิ่งสำคัญที่นี่คือว่าทุกเหตุการณ์ที่เข้าสู่บริบท AngularJS จะเรียกใช้$digestวนซ้ำ นั่นหมายความว่าทุกครั้งที่เราเขียนจดหมายในอินพุตลูปจะทำการตรวจสอบทุกรายการ$watchในหน้านี้

$ ใช้ ()

หากคุณโทร$applyเมื่อมีเหตุการณ์เกิดขึ้นเหตุการณ์นั้นจะเข้าสู่บริบทเชิงมุม แต่ถ้าคุณไม่เรียกมันเหตุการณ์นั้นจะวิ่งออกไปข้างนอก มันง่ายอย่างที่คิด $applyจะเรียก$digest()ลูปภายในและจะวนซ้ำนาฬิกาทั้งหมดเพื่อให้แน่ใจว่า DOM ได้รับการอัปเดตด้วยค่าที่อัพเดตใหม่

$apply()วิธีการจะเรียกดูบนทั้ง$scopeห่วงโซ่ในขณะที่$digest()วิธีการเดียวที่จะเรียกดูในปัจจุบันและ$scope เมื่อไม่มีการสูงขึ้นวัตถุต้องรู้เกี่ยวกับการเปลี่ยนแปลงท้องถิ่นคุณสามารถใช้children$scope$digest()


18

ผมพบว่ามากวิดีโอในเชิงลึกซึ่งปก$watch, $apply, $digestและย่อยในรอบ:

ต่อไปนี้เป็นสไลด์ที่ใช้ในวิดีโอเหล่านั้นเพื่ออธิบายแนวคิด (ในกรณีที่หากลิงก์ด้านบนถูกลบออก / ไม่ทำงาน)

ป้อนคำอธิบายภาพที่นี่

ในภาพด้านบน "$ scope.c" ไม่ได้รับการดูเนื่องจากไม่ได้ใช้ในการผูกข้อมูลใด ๆ (ในมาร์กอัป) อีกสอง ( $scope.aและ$scope.b) จะได้รับการดู

ป้อนคำอธิบายภาพที่นี่

จากภาพด้านบน: ตามเหตุการณ์ของเบราว์เซอร์ที่เกี่ยวข้อง AngularJS จะจับภาพเหตุการณ์ดำเนินการรอบการแยกย่อย (ผ่านนาฬิกาทั้งหมดสำหรับการเปลี่ยนแปลง) ดำเนินการฟังก์ชั่นการเฝ้าดูและอัปเดต DOM ถ้าไม่ได้เหตุการณ์ที่เบราว์เซอร์วงจรย่อยสามารถเรียกตนเองโดยใช้หรือ$apply$digest

เพิ่มเติมเกี่ยวกับ$applyและ$digest:

ป้อนคำอธิบายภาพที่นี่


17

มี$watchGroupและ$watchCollectionเช่นกัน โดยเฉพาะ$watchGroupมีประโยชน์จริง ๆ ถ้าคุณต้องการเรียกใช้ฟังก์ชันเพื่อปรับปรุงวัตถุที่มีคุณสมบัติหลายอย่างในมุมมองที่ไม่ใช่วัตถุ dom เช่นมุมมองอื่นใน canvas, WebGLหรือคำขอเซิร์ฟเวอร์

ที่นี่เอกสารการเชื่อมโยง


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

15

เพิ่งอ่านทุกอย่างที่กล่าวมาข้างต้นเบื่อและง่วงนอน (ขออภัย แต่จริง) มีเทคนิคอย่างลึกซึ้งรายละเอียดและแห้ง ทำไมฉันถึงเขียน เนื่องจาก AngularJS มีขนาดใหญ่แนวคิดการเชื่อมต่อระหว่างกันจำนวนมากสามารถทำให้ใคร ๆ ฉันมักจะถามตัวเองว่าฉันไม่ฉลาดพอที่จะเข้าใจหรือไม่? No! มันเป็นเพราะมีน้อยคนที่สามารถอธิบายเทคโนโลยีในภาษาที่ไม่มีคำศัพท์ทั้งหมด! ตกลงให้ฉันลอง:

1) สิ่งเหล่านี้ล้วนเป็นสิ่งที่ขับเคลื่อนด้วยเหตุการณ์ (ฉันได้ยินเสียงหัวเราะ แต่อ่านต่อ)

หากคุณไม่ทราบว่าเกิดจากเหตุการณ์ใดให้คิดว่าคุณวางปุ่มบนหน้าเว็บเชื่อมต่อกับฟังก์ชั่นโดยใช้ "on-click" รอให้ผู้ใช้คลิกเพื่อเรียกการกระทำที่คุณสร้างไว้ภายใน ฟังก์ชัน หรือคิดว่า "ทริกเกอร์" ของ SQL Server / Oracle

2) $ watch คือ "เมื่อคลิก"

มีอะไรพิเศษเกี่ยวกับมันใช้เวลา 2 ฟังก์ชั่นเป็นพารามิเตอร์หนึ่งคนแรกให้ค่าจากเหตุการณ์ที่สองจะนำมูลค่ามาพิจารณา ...

3) $ digest เป็นหัวหน้าที่ตรวจสอบอย่างไม่รู้จักเหน็ดเหนื่อย bla-bla-bla แต่เป็นเจ้านายที่ดี

4) การใช้ $ จะช่วยให้คุณได้รับเมื่อคุณต้องการทำด้วยตนเองเช่นการป้องกันข้อผิดพลาด (ในกรณีที่การคลิกไม่เริ่มขึ้นคุณบังคับให้มันรัน)

ตอนนี้มาทำให้มันเป็นภาพ นึกภาพสิ่งนี้เพื่อให้ง่ายต่อการคว้าความคิดมากขึ้น:

ในร้านอาหาร,

- รอ

ควรที่จะรับคำสั่งซื้อจากลูกค้านี่คือ

$watch(
  function(){return orders;},
  function(){Kitchen make it;}
);

- ผู้จัดการทำงานเพื่อให้แน่ใจว่าบริกรทุกคนตื่นขึ้นและตอบสนองต่อสัญญาณการเปลี่ยนแปลงใด ๆ จากลูกค้า นี่คือ$digest()

- OWNERมีพลังสูงสุดในการขับเคลื่อนทุกคนเมื่อมีการร้องขอนี่คือ$apply()


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