AngularJS: วิธีดูตัวแปรบริการ


414

ฉันมีบริการพูดว่า:

factory('aService', ['$rootScope', '$resource', function ($rootScope, $resource) {
  var service = {
    foo: []
  };

  return service;
}]);

และฉันต้องการใช้fooเพื่อควบคุมรายการที่แสดงผลเป็น HTML:

<div ng-controller="FooCtrl">
  <div ng-repeat="item in foo">{{ item }}</div>
</div>

เพื่อให้ตัวควบคุมตรวจสอบเมื่อaService.fooมีการปรับปรุงฉันได้ cobbled ร่วมกันรูปแบบนี้ที่ฉันเพิ่มบริการไปยังตัวควบคุม$scopeแล้วใช้$scope.$watch():

function FooCtrl($scope, aService) {                                                                                                                              
  $scope.aService = aService;
  $scope.foo = aService.foo;

  $scope.$watch('aService.foo', function (newVal, oldVal, scope) {
    if(newVal) { 
      scope.foo = newVal;
    }
  });
}

สิ่งนี้ให้ความรู้สึกยาวและฉันได้ทำซ้ำในคอนโทรลเลอร์ทุกตัวที่ใช้ตัวแปรของบริการ มีวิธีที่ดีกว่าในการรับชมตัวแปรที่แชร์หรือไม่


1
คุณสามารถส่งพารามิเตอร์ที่สามไปยัง $ watch set เป็น true เพื่อ aService และ Deep Properties ทั้งหมด
SirTophamHatt

7
$ scope.foo = aService.foo เพียงพอคุณสามารถเสียบรรทัดข้างต้นได้ และสิ่งที่มันไม่ภายใน $ นาฬิกาไม่ได้ทำให้รู้สึกถ้าคุณต้องการที่จะกำหนดค่าใหม่ถึง $ scope.foo เพียงแค่ทำมัน ...
จิน

4
คุณสามารถอ้างอิงaService.fooในมาร์กอัป html ได้ไหม (เช่นนี้: plnkr.co/edit/aNrw5Wo4Q0IxR2loipl5?p=preview )
thetallweeks

1
ฉันได้เพิ่มตัวอย่างโดยไม่มี Callbacks หรือ $ watches ดูคำตอบด้านล่าง ( jsfiddle.net/zymotik/853wvv7s )
Zymotik

1
@ MikeGledhill คุณพูดถูก ฉันคิดว่าเป็นเพราะธรรมชาติของ Javascript คุณสามารถเห็นรูปแบบนี้ในสถานที่อื่น ๆ (ไม่ใช่เฉพาะใน Angular แต่ใน JS พูดโดยทั่วไป) ในอีกด้านหนึ่งคุณถ่ายโอนค่า (และไม่ถูกผูกไว้) และอีกทางหนึ่งที่คุณถ่ายโอนวัตถุ (หรือค่าที่อ้างอิงถึงวัตถุ ... ) และนั่นเป็นสาเหตุที่คุณสมบัติถูกปรับปรุงอย่างถูกต้อง แสดงในตัวอย่างของ Zymotik ด้านบน)
Christophe Vidal

คำตอบ:


277

$watchคุณสามารถใช้รูปแบบการสังเกตการณ์ดีเก่าถ้าคุณต้องการที่จะหลีกเลี่ยงการปกครองแบบเผด็จการและค่าใช้จ่ายของ

ในการบริการ:

factory('aService', function() {
  var observerCallbacks = [];

  //register an observer
  this.registerObserverCallback = function(callback){
    observerCallbacks.push(callback);
  };

  //call this when you know 'foo' has been changed
  var notifyObservers = function(){
    angular.forEach(observerCallbacks, function(callback){
      callback();
    });
  };

  //example of when you may want to notify observers
  this.foo = someNgResource.query().$then(function(){
    notifyObservers();
  });
});

และในตัวควบคุม:

function FooCtrl($scope, aService){
  var updateFoo = function(){
    $scope.foo = aService.foo;
  };

  aService.registerObserverCallback(updateFoo);
  //service now in control of updating foo
};

21
@Moo ฟัง$destoryเหตุการณ์ที่เกิดขึ้นในขอบเขตและเพิ่มวิธีการยกเลิกการลงทะเบียนเพื่อaService
เจมี่

13
อะไรคือข้อดีของการแก้ปัญหานี้? ต้องการรหัสเพิ่มเติมในบริการและมีรหัสในตัวควบคุมค่อนข้างเท่ากัน (เนื่องจากเราต้องยกเลิกการลงทะเบียนกับ $ destroy) ฉันบอกได้ว่าความเร็วในการประหาร แต่ในกรณีส่วนใหญ่มันไม่สำคัญ
Alex Che

6
ไม่แน่ใจว่าวิธีนี้เป็นวิธีแก้ปัญหาที่ดีกว่า $ watch ผู้ถามได้ขอวิธีง่ายๆในการแบ่งปันข้อมูลดูเหมือนว่าจะยุ่งยากกว่าเดิม ฉันต้องการใช้ $ Broadcast มากกว่านี้
Jin

11
$watchรูปแบบผู้สังเกตการณ์ vs ผู้ใช้เลือกว่าจะสำรวจหรือผลักดันและเป็นเรื่องของประสิทธิภาพดังนั้นควรใช้เมื่อประสิทธิภาพมีความสำคัญ ฉันใช้รูปแบบการสังเกตการณ์เมื่อไม่เช่นนั้นฉันจะต้องดูวัตถุที่ซับซ้อน "ลึก" ฉันแนบบริการทั้งหมดกับขอบเขต $ แทนที่จะดูค่าบริการเดียว ฉันหลีกเลี่ยงการเฝ้าดู $ ของเชิงมุมเหมือนปีศาจมีสิ่งที่เกิดขึ้นเพียงพอในคำสั่งและในการผูกข้อมูลเชิงมุมพื้นเมือง
dtheodor

107
เหตุผลที่เราใช้กรอบอย่างแองกูลาร์คือไม่ทำให้รูปแบบการสังเกตการณ์ของเราไม่น่าเบื่อ
รหัส Whisperer

230

ในสถานการณ์เช่นนี้ที่หลาย / วัตถุที่ไม่รู้จักอาจมีความสนใจในการเปลี่ยนแปลงใช้$rootScope.$broadcastจากรายการที่มีการเปลี่ยนแปลง

แทนที่จะสร้างการลงทะเบียนของผู้ฟังของคุณเอง (ซึ่งจะต้องล้างออกด้วยเงินดอลลาร์ที่แตกต่างกัน) คุณควรจะสามารถ$broadcastรับบริการได้

คุณยังคงต้องใช้รหัสตัว$onจัดการในแต่ละฟัง แต่รูปแบบจะถูกแยกออกจากการโทรหลายสายไป$digestและทำให้หลีกเลี่ยงความเสี่ยงของนักดูที่ต้องทำงานเป็นเวลานาน

ด้วยวิธีนี้ผู้ฟังสามารถมาและไปจากDOMและ / หรือขอบเขตลูกที่แตกต่างกันโดยไม่ต้องให้บริการเปลี่ยนพฤติกรรมของมัน

อัปเดต **: ตัวอย่าง **

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

.factory('UserService', [ '$rootScope', function($rootScope) {
   var service = <whatever you do for the object>

   service.save = function(data) {
     .. validate data and update model ..
     // notify listeners and provide the data that changed [optional]
     $rootScope.$broadcast('user:updated',data);
   }

   // alternatively, create a callback function and $broadcast from there if making an ajax call

   return service;
}]);

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

.controller('UserCtrl', [ 'UserService', '$scope', function(UserService, $scope) {

  var user = UserService.getUser();

  // if you don't want to expose the actual object in your scope you could expose just the values, or derive a value for your purposes
   $scope.name = user.firstname + ' ' +user.lastname;

   $scope.$on('user:updated', function(event,data) {
     // you could inspect the data to see if what you care about changed, or just update your own scope
     $scope.name = user.firstname + ' ' + user.lastname;
   });

   // different event names let you group your code and logic by what happened
   $scope.$on('user:logout', function(event,data) {
     .. do something differently entirely ..
   });

 }]);

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

$ ออกอากาศส่งข้อความจากขอบเขตที่เรียกว่าลงไปในขอบเขตเด็ก ๆ ดังนั้นเรียกมันจาก $ rootScope จะยิงทุกขอบเขต ตัวอย่างเช่นหากคุณได้รับ $ Broadcast จากขอบเขตของคอนโทรลเลอร์คุณจะเริ่มทำงานในขอบเขตที่รับมาจากขอบเขตคอนโทรลเลอร์ของคุณเท่านั้น $ emit ไปในทิศทางตรงกันข้ามและมีพฤติกรรมคล้ายกันกับเหตุการณ์ DOM ที่ทำให้เกิดการปะทุขอบเขต

โปรดจำไว้ว่ามีสถานการณ์จำลองที่การออกอากาศของ $ มีความหมายและมีสถานการณ์ที่ $ watch เป็นตัวเลือกที่ดีกว่า - โดยเฉพาะอย่างยิ่งถ้าอยู่ในขอบเขตแยกด้วยการแสดงออกของนาฬิกาที่เฉพาะเจาะจงมาก


1
การออกจากวงจรย่อยของ $ เป็นสิ่งที่ดีโดยเฉพาะอย่างยิ่งหากการเปลี่ยนแปลงที่คุณเฝ้าดูไม่ใช่ค่าที่จะเข้าสู่ DOM โดยตรงและทันที
XML

มีวิธีหลีกเลี่ยงการบันทึก. () ดูเหมือนว่า overkill เมื่อคุณเพิ่งตรวจสอบการปรับปรุงตัวแปรเดียวใน sharedService เราสามารถดูตัวแปรจากภายใน SharedService และออกอากาศเมื่อมีการเปลี่ยนแปลงได้หรือไม่
JerryKur

ฉันพยายามใช้ข้อมูลร่วมกันระหว่างตัวควบคุมค่อนข้างน้อย แต่นี่เป็นวิธีเดียวที่ทำงานได้ เล่นได้ดีครับ
abettermap

ฉันชอบสิ่งนี้กับคำตอบอื่น ๆ ดูเหมือนแฮ็กน้อยกว่านี้ขอบคุณ
JMK

9
นี่เป็นรูปแบบการออกแบบที่ถูกต้องเฉพาะเมื่อคอนโทรลเลอร์ที่คุณใช้มีแหล่งข้อมูลที่เป็นไปได้หลายแหล่งเท่านั้น กล่าวอีกนัยหนึ่งถ้าคุณมีสถานการณ์ MIMO (หลายอินพุต / หลายเอาท์พุท) หากคุณเพียงแค่ใช้รูปแบบหนึ่งต่อหลายคุณควรใช้การอ้างอิงวัตถุโดยตรงและให้กรอบ Angular ทำการผูกสองทางสำหรับคุณ Horkyze เชื่อมโยงด้านล่างนี้และเป็นคำอธิบายที่ดีเกี่ยวกับการเชื่อมโยงสองทางอัตโนมัติและข้อ จำกัด : stsc3000.github.io/blog/2013/10/26/…
Charles

47

ฉันใช้วิธีการที่คล้ายกันเป็น @dtheodot แต่ใช้สัญญาเชิงมุมแทนการโทรกลับ

app.service('myService', function($q) {
    var self = this,
        defer = $q.defer();

    this.foo = 0;

    this.observeFoo = function() {
        return defer.promise;
    }

    this.setFoo = function(foo) {
        self.foo = foo;
        defer.notify(self.foo);
    }
})

จากนั้นไม่ว่าจะใช้myService.setFoo(foo)วิธีใดในการอัปเดตfooบริการ ในตัวควบคุมของคุณคุณสามารถใช้มันเป็น:

myService.observeFoo().then(null, null, function(foo){
    $scope.foo = foo;
})

ข้อโต้แย้งสองข้อแรกthenคือการโทรกลับสำเร็จและข้อผิดพลาดข้อที่สามแจ้งเตือนการโทรกลับ

ข้อมูลอ้างอิงสำหรับ $ q


อะไรคือข้อดีของวิธีนี้ในการออกอากาศ $ ที่ได้อธิบายโดย Matt Pileggi
Fabio

ทั้งสองวิธีมีประโยชน์ ข้อดีของการออกอากาศสำหรับฉันคือความสามารถในการอ่านของมนุษย์และความเป็นไปได้ในการรับฟังสถานที่มากขึ้นในกิจกรรมเดียวกัน ฉันเดาว่าข้อเสียเปรียบหลักคือการออกอากาศกำลังส่งข้อความไปยังขอบเขตผู้สืบทอดทั้งหมดดังนั้นจึงอาจเป็นปัญหาด้านประสิทธิภาพ
Krym

2
ฉันมีปัญหาในการทำงาน$scope.$watchกับตัวแปรบริการที่ดูเหมือนว่าจะไม่ทำงาน (ขอบเขตที่ฉันดูอยู่นั้นเป็นแบบที่สืบทอดมาจาก$rootScope) - มันใช้งานได้ เคล็ดลับเยี่ยมสำหรับการแบ่งปัน!
Seiyria

4
คุณจะทำความสะอาดหลังจากตัวเองด้วยวิธีนี้ได้อย่างไร เป็นไปได้ไหมที่จะลบการเรียกกลับที่ลงทะเบียนไว้ออกจากสัญญาเมื่อขอบเขตถูกทำลาย?
Abris

คำถามที่ดี. ฉันไม่รู้จริงๆ ฉันจะพยายามทำการทดสอบบางอย่างเกี่ยวกับวิธีการที่คุณสามารถลบการแจ้งเตือนการโทรกลับจากสัญญา
Krym

41

ไม่มีนาฬิกาหรือผู้โทรกลับผู้สังเกตการณ์ ( http://jsfiddle.net/zymotik/853wvv7s/ ):

JavaScript:

angular.module("Demo", [])
    .factory("DemoService", function($timeout) {

        function DemoService() {
            var self = this;
            self.name = "Demo Service";

            self.count = 0;

            self.counter = function(){
                self.count++;
                $timeout(self.counter, 1000);
            }

            self.addOneHundred = function(){
                self.count+=100;
            }

            self.counter();
        }

        return new DemoService();

    })
    .controller("DemoController", function($scope, DemoService) {

        $scope.service = DemoService;

        $scope.minusOneHundred = function() {
            DemoService.count -= 100;
        }

    });

HTML

<div ng-app="Demo" ng-controller="DemoController">
    <div>
        <h4>{{service.name}}</h4>
        <p>Count: {{service.count}}</p>
    </div>
</div>

JavaScript นี้ใช้งานได้เมื่อเราส่งวัตถุกลับจากบริการแทนที่จะเป็นค่า เมื่อวัตถุ JavaScript ถูกส่งคืนจากบริการ Angular จะเพิ่มการเฝ้าดูไปยังคุณสมบัติทั้งหมด

นอกจากนี้โปรดทราบว่าฉันใช้ 'var self = this' เพราะฉันต้องทำการอ้างอิงไปยังวัตถุต้นฉบับเมื่อมีการดำเนินการ $ timeout มิฉะนั้น 'this' จะอ้างถึงวัตถุหน้าต่าง


3
นี่เป็นวิธีที่ยอดเยี่ยม! มีวิธีผูกเฉพาะคุณสมบัติของบริการกับขอบเขตแทนบริการทั้งหมดหรือไม่ แค่ทำ$scope.count = service.countไม่ได้ผล
jvannistelrooy

คุณยังสามารถซ้อนคุณสมบัติภายในของวัตถุ (โดยพลการ) เพื่อให้มันผ่านการอ้างอิง $scope.data = service.data <p>Count: {{ data.count }}</p>
Alex Ross

1
วิธีการที่ยอดเยี่ยม! ในขณะที่มีจำนวนมากคำตอบการทำงานที่แข็งแกร่งในหน้านี้นี่คือ a) ที่ง่ายที่สุดในการใช้และ b) เข้าใจง่ายที่สุดเมื่ออ่านรหัส คำตอบนี้ควรจะสูงกว่าที่เป็นอยู่ในปัจจุบันมาก
CodeMoose

ขอบคุณ @CodeMoose ฉันได้ลดความซับซ้อนลงไปอีกในวันนี้สำหรับผู้ที่เพิ่งรู้จักกับ AngularJS / JavaScript
Zymotik

2
ขอพระเจ้าอวยพรคุณ ฉันเสียเวลาไปหลายล้านชั่วโมง เพราะฉันกำลังดิ้นรนกับ 1.5 และ angularjs เปลี่ยนจาก 1 เป็น 2 และยังต้องการแบ่งปันข้อมูล
Amna

29

ฉันสะดุดกับคำถามนี้ที่กำลังมองหาสิ่งที่คล้ายกัน แต่ฉันคิดว่ามันสมควรได้รับการอธิบายอย่างละเอียดเกี่ยวกับสิ่งที่เกิดขึ้นรวมถึงการแก้ปัญหาเพิ่มเติมบางอย่าง

เมื่อการแสดงออกเชิงมุมดังกล่าวเป็นหนึ่งที่คุณใช้เป็นปัจจุบันใน HTML, เชิงมุมชุดโดยอัตโนมัติขึ้น$watchสำหรับ$scope.foo, และจะปรับปรุง HTML เมื่อใดก็ตามที่$scope.fooมีการเปลี่ยนแปลง

<div ng-controller="FooCtrl">
  <div ng-repeat="item in foo">{{ item }}</div>
</div>

ปัญหาที่ยังไม่ได้พูดในที่นี้คือหนึ่งในสองสิ่งที่ส่งผลกระทบต่อaService.foo การเปลี่ยนแปลงจะไม่ถูกตรวจสอบ ความเป็นไปได้ทั้งสองนี้คือ:

  1. aService.foo กำลังได้รับการตั้งค่าให้เป็นอาร์เรย์ใหม่ในแต่ละครั้งทำให้การอ้างอิงนั้นล้าสมัย
  2. aService.fooกำลังอัปเดตในลักษณะที่$digestไม่ได้เรียกใช้รอบในการอัปเดต

ปัญหาที่ 1: การอ้างอิงที่ล้าสมัย

พิจารณาความเป็นไปได้ครั้งแรกโดยสมมติว่า a $digestกำลังถูกนำไปใช้หากaService.fooเป็นอาร์เรย์เดียวกันเสมอชุดที่ตั้งโดยอัตโนมัติ$watchจะตรวจจับการเปลี่ยนแปลงดังที่แสดงในข้อมูลโค้ดด้านล่าง

โซลูชันที่ 1-a: ตรวจสอบให้แน่ใจว่าอาร์เรย์หรือวัตถุนั้นเป็นวัตถุเดียวกันในการปรับปรุงแต่ละครั้ง

ในขณะที่คุณสามารถดู ng ซ้ำที่แนบมาเพื่อที่จะaService.fooไม่ปรับปรุงเมื่อaService.fooการเปลี่ยนแปลง แต่ NG-ซ้ำที่แนบมากับไม่aService2.foo นี่เป็นเพราะการอ้างอิงของเราaService.fooล้าสมัย แต่การอ้างอิงของเราaService2.fooไม่ใช่ เราสร้างการอ้างอิงไปยังอาร์เรย์เริ่มต้นด้วย$scope.foo = aService.foo;ซึ่งถูกยกเลิกโดยบริการในการอัปเดตครั้งถัดไปซึ่งหมายถึง$scope.fooไม่ได้อ้างอิงอาร์เรย์ที่เราต้องการอีกต่อไป

อย่างไรก็ตามในขณะที่มีหลายวิธีในการตรวจสอบให้แน่ใจว่าการอ้างอิงเริ่มต้นถูกเก็บไว้ในชั้นเชิงบางครั้งอาจจำเป็นต้องเปลี่ยนวัตถุหรืออาร์เรย์ หรือสิ่งที่ถ้าอ้างอิงคุณสมบัติบริการดั้งเดิมเหมือนStringหรือNumber? ในกรณีเหล่านั้นเราไม่สามารถอ้างอิงได้อย่างง่ายดาย ดังนั้นสิ่งที่สามารถเราจะทำอย่างไร

คำตอบหลายข้อที่ให้ไว้ก่อนหน้านี้ได้ให้วิธีแก้ปัญหาแล้ว อย่างไรก็ตามฉันเองชอบใช้วิธีง่าย ๆ ที่แนะนำโดยจินและthewallweekในความคิดเห็น:

เพียงอ้างอิง aService.foo ในมาร์กอัป html

โซลูชัน 1-b: แนบบริการกับขอบเขตและการอ้างอิง{service}.{property}ใน HTML

ความหมายเพียงทำสิ่งนี้:

HTML:

<div ng-controller="FooCtrl">
  <div ng-repeat="item in aService.foo">{{ item }}</div>
</div>

JS:

function FooCtrl($scope, aService) {
    $scope.aService = aService;
}

ด้วยวิธีนี้การ$watchแก้ไขจะดำเนินการaService.fooในแต่ละครั้ง$digestซึ่งจะได้รับค่าการอัปเดตที่ถูกต้อง

นี่คือสิ่งที่คุณพยายามทำกับวิธีแก้ปัญหาของคุณ แต่ในทางรอบน้อยกว่ามาก คุณเพิ่มที่ไม่จำเป็น$watchในการควบคุมที่ชัดเจนทำให้fooใน$scopeทุกครั้งที่มีการเปลี่ยนแปลง คุณไม่จำเป็นต้องมีสิ่งนั้นเพิ่มเติม$watchเมื่อคุณแนบaServiceแทนที่จะaService.fooเข้ากับ$scopeและผูกไว้อย่างชัดเจนaService.fooในมาร์กอัป


ตอนนี้ทุกอย่างดีและดีสมมติว่ามีการใช้$digestวัฏจักร ในตัวอย่างของฉันด้านบนฉันใช้$intervalบริการของ Angular เพื่ออัปเดตอาร์เรย์ซึ่งเริ่มการ$digestวนซ้ำอัตโนมัติหลังจากการอัปเดตแต่ละครั้ง แต่ถ้าตัวแปรบริการ (ไม่ว่าด้วยเหตุผลใดก็ตาม) จะไม่ได้รับการอัปเดตภายใน "โลกเชิงมุม" ในคำอื่น ๆ ที่เราdontมี$digestวงจรถูกเปิดใช้งานโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลงสถานที่ให้บริการหรือไม่


ปัญหาที่ 2: ไม่มี $digest

วิธีแก้ปัญหาหลายอย่างที่นี่จะแก้ปัญหานี้ แต่ฉันเห็นด้วยกับCode Whisperer :

เหตุผลที่เราใช้กรอบอย่างแองกูลาร์คือไม่ทำให้รูปแบบการสังเกตการณ์ของเราเองไม่น่าเบื่อ

ดังนั้นฉันต้องการใช้การaService.fooอ้างอิงในมาร์กอัพ HTML ดังที่แสดงในตัวอย่างที่สองข้างต้นและไม่ต้องลงทะเบียนการติดต่อกลับเพิ่มเติมภายในคอนโทรลเลอร์

โซลูชันที่ 2: ใช้ตัวตั้งค่าและทะเยอทะยานด้วย $rootScope.$apply()

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

factory('aService', [
  '$rootScope',
  function($rootScope) {
    var realFoo = [];

    var service = {
      set foo(a) {
        realFoo = a;
        $rootScope.$apply();
      },
      get foo() {
        return realFoo;
      }
    };
  // ...
}

นี่ฉันเพิ่มตัวแปร 'ส่วนตัว' realFooในการทำงานบริการ: สิ่งนี้รับการปรับปรุงและดึงข้อมูลโดยใช้get foo()และset foo()ฟังก์ชั่นตามลำดับบนserviceวัตถุ

หมายเหตุการใช้งาน$rootScope.$apply()ในฟังก์ชั่นการตั้งค่า service.fooเพื่อให้แน่ใจว่าเชิงมุมจะต้องตระหนักถึงการเปลี่ยนแปลงใด ๆ ถ้าคุณได้รับข้อผิดพลาด 'inprog' ดูหน้าอ้างอิงนี้มีประโยชน์หรือถ้าคุณใช้เชิงมุม> = 1.3 $rootScope.$applyAsync()คุณสามารถเพียงแค่การใช้งาน

นอกจากนี้ระวังสิ่งนี้หากaService.fooมีการอัพเดทบ่อยมากเนื่องจากอาจมีผลกระทบต่อประสิทธิภาพอย่างมาก หากประสิทธิภาพการทำงานจะเป็นปัญหาคุณสามารถตั้งค่ารูปแบบการสังเกตการณ์คล้ายกับคำตอบอื่น ๆ ที่นี่โดยใช้ setter


3
นี่คือทางออกที่ถูกต้องและง่ายที่สุด ดังที่ @NanoWizard พูดว่า $ digest จะเฝ้าดูservicesไม่ใช่คุณสมบัติที่เป็นของบริการ
Sarpdoruk Tahmaz

28

เท่าที่ฉันสามารถบอกได้คุณไม่ต้องทำอะไรที่ซับซ้อนเหมือนอย่างนั้น คุณได้กำหนด foo จากบริการไปยังขอบเขตของคุณแล้วและเนื่องจาก foo เป็นอาร์เรย์ (และในทางกลับกันวัตถุจะถูกกำหนดโดยการอ้างอิง!) ดังนั้นสิ่งที่คุณต้องทำคือสิ่งนี้:

function FooCtrl($scope, aService) {                                                                                                                              
  $scope.foo = aService.foo;

 }

หากมีบางตัวแปรอื่น ๆ ใน Ctrl เดียวกันนี้ขึ้นอยู่กับการเปลี่ยนแปลง foo ดังนั้นใช่คุณจะต้องมีนาฬิกาเพื่อสังเกต foo และทำการเปลี่ยนแปลงกับตัวแปรนั้น แต่ตราบใดที่มันดูง่าย ๆ ก็ไม่จำเป็นต้องอ้างอิง หวังว่านี่จะช่วยได้


35
ฉันพยายามและฉันไม่$watchสามารถทำงานกับแบบดั้งเดิมได้ แต่ฉันกำหนดวิธีการในการให้บริการที่จะคืนค่าดั้งเดิม: somePrimitive() = function() { return somePrimitive }และผมได้รับมอบหมายคุณสมบัติ $ $scope.somePrimitive = aService.somePrimitive;ขอบเขตวิธีการที่: จากนั้นฉันใช้เมธอดขอบเขตใน HTML: <span>{{somePrimitive()}}</span>
ทำเครื่องหมาย Rajcok

4
@MarkRajcok ไม่ต้องใช้พื้นฐาน เพิ่มไว้ในวัตถุ การใช้งานดั้งเดิมไม่สามารถเปลี่ยนแปลงได้และการผูกข้อมูล 2way จะไม่ทำงาน
Jimmy Kane

3
@ JimmyKane ใช่ไม่ควรใช้ primitives สำหรับการบันทึกข้อมูลแบบสองทาง แต่ฉันคิดว่าคำถามเกี่ยวกับการดูตัวแปรบริการไม่ได้ตั้งค่าการเชื่อมโยงแบบสองทาง หากคุณต้องการดูคุณสมบัติ / ตัวแปรบริการไม่จำเป็นต้องใช้วัตถุ - สามารถใช้แบบดั้งเดิมได้
Mark Rajcok

3
ในการตั้งค่านี้ฉันสามารถเปลี่ยนค่าบริการจากขอบเขต แต่ขอบเขตไม่เปลี่ยนแปลงในการตอบสนองการเปลี่ยนแปลงการบริการ
Ouwen Huang

4
สิ่งนี้ไม่ได้ผลสำหรับฉัน เพียงแค่การกำหนด$scope.foo = aService.fooไม่อัปเดตตัวแปรขอบเขตโดยอัตโนมัติ
ดาร์วินเทค

9

คุณสามารถแทรกบริการใน $ rootScope และดู:

myApp.run(function($rootScope, aService){
    $rootScope.aService = aService;
    $rootScope.$watch('aService', function(){
        alert('Watch');
    }, true);
});

ในตัวควบคุมของคุณ:

myApp.controller('main', function($scope){
    $scope.aService.foo = 'change';
});

ตัวเลือกอื่นคือใช้ไลบรารีภายนอกเช่น: https://github.com/melanke/Watch.JS

ทำงานร่วมกับ: IE 9+, FF 4+, SF 5+, WebKit, CH 7+, OP 12+, BESEN, Node.JS, Rhino 1.7+

คุณสามารถสังเกตเห็นการเปลี่ยนแปลงของหนึ่งคุณลักษณะวัตถุหลายอย่างหรือทั้งหมด

ตัวอย่าง:

var ex3 = {
    attr1: 0,
    attr2: "initial value of attr2",
    attr3: ["a", 3, null]
};   
watch(ex3, function(){
    alert("some attribute of ex3 changes!");
});
ex3.attr3.push("new value");​

2
ฉันไม่อยากเชื่อคำตอบนี้ไม่ใช่ผู้โหวตมากที่สุด !!! นี่เป็นโซลูชันที่หรูหราที่สุด (IMO) เนื่องจากช่วยลดการรับ - ส่งข้อมูลและอาจลดความจำเป็นในการใช้เครื่องมือจัดการสื่อกลางเพิ่มเติม ฉันจะลงคะแนนให้มากกว่านี้หากทำได้ ...
โคดี

การเพิ่มบริการทั้งหมดของคุณไปยัง $ rootScope ผลประโยชน์และข้อผิดพลาดที่อาจเกิดขึ้นมีรายละเอียดที่นี่: stackoverflow.com/questions/14573023/ …
Zymotik

6

คุณสามารถรับชมการเปลี่ยนแปลงภายในโรงงานจากนั้นทำการถ่ายทอดการเปลี่ยนแปลง

angular.module('MyApp').factory('aFactory', function ($rootScope) {
    // Define your factory content
    var result = {
        'key': value
    };

    // add a listener on a key        
    $rootScope.$watch(function () {
        return result.key;
    }, function (newValue, oldValue, scope) {
        // This is called after the key "key" has changed, a good idea is to broadcast a message that key has changed
        $rootScope.$broadcast('aFactory:keyChanged', newValue);
    }, true);

    return result;
});

จากนั้นในตัวควบคุมของคุณ:

angular.module('MyApp').controller('aController', ['$rootScope', function ($rootScope) {

    $rootScope.$on('aFactory:keyChanged', function currentCityChanged(event, value) {
        // do something
    });
}]);

ในลักษณะนี้คุณใส่รหัสโรงงานที่เกี่ยวข้องทั้งหมดไว้ในคำอธิบายแล้วคุณสามารถพึ่งพาการถ่ายทอดจากภายนอกเท่านั้น


6

== == ปรับปรุง

ง่ายมากในตอนนี้ใน $ watch

ปากกาที่นี่

HTML:

<div class="container" data-ng-app="app">

  <div class="well" data-ng-controller="FooCtrl">
    <p><strong>FooController</strong></p>
    <div class="row">
      <div class="col-sm-6">
        <p><a href="" ng-click="setItems([ { name: 'I am single item' } ])">Send one item</a></p>
        <p><a href="" ng-click="setItems([ { name: 'Item 1 of 2' }, { name: 'Item 2 of 2' } ])">Send two items</a></p>
        <p><a href="" ng-click="setItems([ { name: 'Item 1 of 3' }, { name: 'Item 2 of 3' }, { name: 'Item 3 of 3' } ])">Send three items</a></p>
      </div>
      <div class="col-sm-6">
        <p><a href="" ng-click="setName('Sheldon')">Send name: Sheldon</a></p>
        <p><a href="" ng-click="setName('Leonard')">Send name: Leonard</a></p>
        <p><a href="" ng-click="setName('Penny')">Send name: Penny</a></p>
      </div>
    </div>
  </div>

  <div class="well" data-ng-controller="BarCtrl">
    <p><strong>BarController</strong></p>
    <p ng-if="name">Name is: {{ name }}</p>
    <div ng-repeat="item in items">{{ item.name }}</div>
  </div>

</div>

JavaScript:

var app = angular.module('app', []);

app.factory('PostmanService', function() {
  var Postman = {};
  Postman.set = function(key, val) {
    Postman[key] = val;
  };
  Postman.get = function(key) {
    return Postman[key];
  };
  Postman.watch = function($scope, key, onChange) {
    return $scope.$watch(
      // This function returns the value being watched. It is called for each turn of the $digest loop
      function() {
        return Postman.get(key);
      },
      // This is the change listener, called when the value returned from the above function changes
      function(newValue, oldValue) {
        if (newValue !== oldValue) {
          // Only update if the value changed
          $scope[key] = newValue;
          // Run onChange if it is function
          if (angular.isFunction(onChange)) {
            onChange(newValue, oldValue);
          }
        }
      }
    );
  };
  return Postman;
});

app.controller('FooCtrl', ['$scope', 'PostmanService', function($scope, PostmanService) {
  $scope.setItems = function(items) {
    PostmanService.set('items', items);
  };
  $scope.setName = function(name) {
    PostmanService.set('name', name);
  };
}]);

app.controller('BarCtrl', ['$scope', 'PostmanService', function($scope, PostmanService) {
  $scope.items = [];
  $scope.name = '';
  PostmanService.watch($scope, 'items');
  PostmanService.watch($scope, 'name', function(newVal, oldVal) {
    alert('Hi, ' + newVal + '!');
  });
}]);

1
ฉันชอบ PostmanService แต่ฉันต้องเปลี่ยนฟังก์ชั่น $ watch บนคอนโทรลเลอร์ได้อย่างไรถ้าฉันต้องการฟังมากกว่าหนึ่งตัวแปร?
jedi

สวัสดีเจไดขอบคุณสำหรับหัวขึ้น! ฉันปรับปรุงปากกาและคำตอบ ฉันขอแนะนำให้เพิ่มฟังก์ชั่นนาฬิกาอีกอันสำหรับเรื่องนั้น ดังนั้นฉันจึงเพิ่มคุณสมบัติใหม่ให้กับ PostmanService ฉันหวังว่านี้จะช่วยให้ :)
hayatbiralem

ที่จริงใช่แล้ว :) ถ้าคุณแบ่งปันรายละเอียดของปัญหาบางทีฉันสามารถช่วยคุณได้
hayatbiralem

4

สร้างคำตอบของ dtheodorคุณสามารถใช้สิ่งที่คล้ายกับด้านล่างเพื่อให้แน่ใจว่าคุณไม่ลืมที่จะยกเลิกการลงทะเบียนโทรกลับ ... บางคนอาจคัดค้านการส่งผ่าน$scopeไปยังบริการ

factory('aService', function() {
  var observerCallbacks = [];

  /**
   * Registers a function that will be called when
   * any modifications are made.
   *
   * For convenience the callback is called immediately after registering
   * which can be prevented with `preventImmediate` param.
   *
   * Will also automatically unregister the callback upon scope destory.
   */
  this.registerObserver = function($scope, cb, preventImmediate){
    observerCallbacks.push(cb);

    if (preventImmediate !== true) {
      cb();
    }

    $scope.$on('$destroy', function () {
      observerCallbacks.remove(cb);
    });
  };

  function notifyObservers() {
    observerCallbacks.forEach(function (cb) {
      cb();
    });
  };

  this.foo = someNgResource.query().$then(function(){
    notifyObservers();
  });
});

Array.remove เป็นวิธีส่วนขยายซึ่งมีลักษณะดังนี้:

/**
 * Removes the given item the current array.
 *
 * @param  {Object}  item   The item to remove.
 * @return {Boolean}        True if the item is removed.
 */
Array.prototype.remove = function (item /*, thisp */) {
    var idx = this.indexOf(item);

    if (idx > -1) {
        this.splice(idx, 1);

        return true;
    }
    return false;
};

2

นี่คือวิธีการทั่วไปของฉัน

mainApp.service('aService',[function(){
        var self = this;
        var callbacks = {};

        this.foo = '';

        this.watch = function(variable, callback) {
            if (typeof(self[variable]) !== 'undefined') {
                if (!callbacks[variable]) {
                    callbacks[variable] = [];
                }
                callbacks[variable].push(callback);
            }
        }

        this.notifyWatchersOn = function(variable) {
            if (!self[variable]) return;
            if (!callbacks[variable]) return;

            angular.forEach(callbacks[variable], function(callback, key){
                callback(self[variable]);
            });
        }

        this.changeFoo = function(newValue) {
            self.foo = newValue;
            self.notifyWatchersOn('foo');
        }

    }]);

ในตัวควบคุมของคุณ

function FooCtrl($scope, aService) {
    $scope.foo;

    $scope._initWatchers = function() {
        aService.watch('foo', $scope._onFooChange);
    }

    $scope._onFooChange = function(newValue) {
        $scope.foo = newValue;
    }

    $scope._initWatchers();

}

FooCtrl.$inject = ['$scope', 'aService'];

2

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

function watch(target, callback, deep) {
    $rootScope.$watch(function () {return eval(target);}, callback, deep);
};

2

ในขณะที่เผชิญกับปัญหาที่คล้ายกันมากฉันดูฟังก์ชั่นในขอบเขตและมีฟังก์ชั่นกลับตัวแปรบริการ ฉันได้สร้างซอ js คุณสามารถค้นหารหัสด้านล่าง

    var myApp = angular.module("myApp",[]);

myApp.factory("randomService", function($timeout){
    var retValue = {};
    var data = 0;

    retValue.startService = function(){
        updateData();
    }

    retValue.getData = function(){
        return data;
    }

    function updateData(){
        $timeout(function(){
            data = Math.floor(Math.random() * 100);
            updateData()
        }, 500);
    }

    return retValue;
});

myApp.controller("myController", function($scope, randomService){
    $scope.data = 0;
    $scope.dataUpdated = 0;
    $scope.watchCalled = 0;
    randomService.startService();

    $scope.getRandomData = function(){
        return randomService.getData();    
    }

    $scope.$watch("getRandomData()", function(newValue, oldValue){
        if(oldValue != newValue){
            $scope.data = newValue;
            $scope.dataUpdated++;
        }
            $scope.watchCalled++;
    });
});

2

ฉันมาที่คำถามนี้ แต่กลับกลายเป็นว่าปัญหาของฉันคือฉันใช้ setInterval เมื่อฉันควรใช้ผู้ให้บริการช่วงเชิงมุม $ นี่เป็นกรณีสำหรับ setTimeout (ใช้ $ timeout แทน) ฉันรู้ว่ามันไม่ใช่คำตอบสำหรับคำถามของ OP แต่อาจช่วยได้เพราะมันช่วยฉันได้


คุณสามารถใช้setTimeoutหรือฟังก์ชั่นที่ไม่ใช่เชิงมุมอื่น ๆ $scope.$apply()แต่ก็ไม่ลืมที่จะตัดรหัสในการติดต่อกลับด้วย
magnetronnie

2

ฉันได้พบวิธีแก้ปัญหาที่ดีมากในหัวข้ออื่น ๆ ที่มีปัญหาที่คล้ายกัน แต่วิธีการที่แตกต่างกันโดยสิ้นเชิง แหล่งที่มา: AngularJS: $ watch ภายในคำสั่งไม่ทำงานเมื่อค่า $ rootScope เปลี่ยนไป

โดยทั่วไปวิธีการแก้ปัญหามีบอกไม่ให้ใช้$watchเพราะมันเป็นทางออกที่หนักมาก แต่พวกเขาเสนอให้ใช้และ$emit$on

ปัญหาของฉันคือการดูตัวแปรในของฉันให้บริการและตอบสนองในคำสั่ง และด้วยวิธีการข้างต้นมันง่ายมาก!

ตัวอย่างโมดูล / บริการของฉัน:

angular.module('xxx').factory('example', function ($rootScope) {
    var user;

    return {
        setUser: function (aUser) {
            user = aUser;
            $rootScope.$emit('user:change');
        },
        getUser: function () {
            return (user) ? user : false;
        },
        ...
    };
});

ดังนั้นโดยทั่วไปฉันดูฉันuser - เมื่อใดก็ตามที่มีการตั้งค่าใหม่ผมสถานะ$emituser:change

ตอนนี้ในกรณีของฉันในคำสั่งที่ฉันใช้:

angular.module('xxx').directive('directive', function (Auth, $rootScope) {
    return {
        ...
        link: function (scope, element, attrs) {
            ...
            $rootScope.$on('user:change', update);
        }
    };
});

ตอนนี้อยู่ในคำสั่งผมฟังบน$rootScopeและในการเปลี่ยนแปลงให้ - ฉันตอบสนองตามลำดับ ง่ายและสง่างามมาก!


1

// บริการ: (ไม่มีอะไรพิเศษที่นี่)

myApp.service('myService', function() {
  return { someVariable:'abc123' };
});

// ctrl:

myApp.controller('MyCtrl', function($scope, myService) {

  $scope.someVariable = myService.someVariable;

  // watch the service and update this ctrl...
  $scope.$watch(function(){
    return myService.someVariable;
  }, function(newValue){
    $scope.someVariable = newValue;
  });
});

1

ค่อนข้างน่าเกลียด แต่ฉันได้เพิ่มการลงทะเบียนตัวแปรขอบเขตให้กับบริการของฉันสำหรับการสลับ:

myApp.service('myService', function() {
    var self = this;
    self.value = false;
    self.c2 = function(){};
    self.callback = function(){
        self.value = !self.value; 
       self.c2();
    };

    self.on = function(){
        return self.value;
    };

    self.register = function(obj, key){ 
        self.c2 = function(){
            obj[key] = self.value; 
            obj.$apply();
        } 
    };

    return this;
});

จากนั้นในตัวควบคุม:

function MyCtrl($scope, myService) {
    $scope.name = 'Superhero';
    $scope.myVar = false;
    myService.register($scope, 'myVar');
}

ขอบคุณ คำถามเล็ก ๆ น้อย ๆ : ทำไมคุณกลับมาthisจากการให้บริการที่แทนself?
shrekuu

4
เพราะความผิดพลาดเกิดขึ้นในบางครั้ง ;-)
nclu

แนวปฏิบัติที่ดีในการreturn this;ออกจากตัวสร้างของคุณ ;-)
โคดี

1

ลองดูที่พลั่วเกอร์นี้ :: นี่เป็นตัวอย่างที่ง่ายที่สุดที่ฉันคิดได้

http://jsfiddle.net/HEdJF/

<div ng-app="myApp">
    <div ng-controller="FirstCtrl">
        <input type="text" ng-model="Data.FirstName"><!-- Input entered here -->
        <br>Input is : <strong>{{Data.FirstName}}</strong><!-- Successfully updates here -->
    </div>
    <hr>
    <div ng-controller="SecondCtrl">
        Input should also be here: {{Data.FirstName}}<!-- How do I automatically updated it here? -->
    </div>
</div>



// declare the app with no dependencies
var myApp = angular.module('myApp', []);
myApp.factory('Data', function(){
   return { FirstName: '' };
});

myApp.controller('FirstCtrl', function( $scope, Data ){
    $scope.Data = Data;
});

myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.Data = Data;
});

0

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

ฉันอาจจะสายไปหน่อย แต่มันก็ง่ายอย่างนี้

ฟังก์ชั่นนาฬิกาเฝ้าดูการเปลี่ยนแปลงการอ้างอิง (ชนิดดั้งเดิม) หากคุณต้องการดูสิ่งต่างๆเช่นการกดอาเรย์

someArray.push(someObj); someArray = someArray.splice(0);

นี่จะอัปเดตข้อมูลอ้างอิงและอัปเดตนาฬิกาได้จากทุกที่ รวมถึงวิธีการให้บริการที่ได้รับ สิ่งใดก็ตามที่เป็นพื้นฐานจะได้รับการอัปเดตโดยอัตโนมัติ


0

ฉันมาสาย แต่ฉันพบวิธีที่ดีกว่าการทำเช่นนี้มากกว่าคำตอบที่โพสต์ข้างต้น แทนที่จะกำหนดตัวแปรให้เก็บค่าตัวแปรบริการฉันสร้างฟังก์ชั่นที่แนบมากับขอบเขตซึ่งจะส่งกลับค่าตัวแปรบริการ

ตัวควบคุม

$scope.foo = function(){
 return aService.foo;
}

ฉันคิดว่านี่จะทำสิ่งที่คุณต้องการ คอนโทรลเลอร์ของฉันทำการตรวจสอบมูลค่าของบริการของฉันด้วยการใช้งานนี้ จริงๆแล้วมันง่ายกว่าคำตอบที่เลือก


ทำไมมันถึงถูกลดลง .. ฉันยังใช้เทคนิคที่คล้ายกันหลายครั้งและใช้งานได้
ไม่ได้กำหนด

0

ฉันได้เขียนบริการสาธารณูปโภคสองอย่างที่ช่วยฉันติดตามการเปลี่ยนแปลงคุณสมบัติของบริการ

หากคุณต้องการข้ามคำอธิบายที่ยาวคุณสามารถข้ามช่องแคบjsfiddle ได้

  1. WatchObj

mod.service('WatchObj', ['$rootScope', WatchObjService]);

function WatchObjService($rootScope) {
  // returns watch function
  // obj: the object to watch for
  // fields: the array of fields to watch
  // target: where to assign changes (usually it's $scope or controller instance)
  // $scope: optional, if not provided $rootScope is use
  return function watch_obj(obj, fields, target, $scope) {
    $scope = $scope || $rootScope;
    //initialize watches and create an array of "unwatch functions"
    var watched = fields.map(function(field) {
      return $scope.$watch(
        function() {
          return obj[field];
        },
        function(new_val) {
          target[field] = new_val;
        }
      );
    });
    //unregister function will unregister all our watches
    var unregister = function unregister_watch_obj() {
      watched.map(function(unregister) {
        unregister();
      });
    };
    //automatically unregister when scope is destroyed
    $scope.$on('$destroy', unregister);
    return unregister;
  };
}

บริการนี้ใช้ในคอนโทรลเลอร์ในวิธีต่อไปนี้: สมมติว่าคุณมีบริการ "testService" พร้อมด้วยคุณสมบัติ 'prop1', 'prop2', 'prop3' คุณต้องการดูและกำหนดขอบเขต 'prop1' และ 'prop2' ด้วยบริการนาฬิกามันจะเป็นดังนี้:

app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);

function TestWatchCtrl($scope, testService, watch) {
  $scope.prop1 = testService.prop1;
  $scope.prop2 = testService.prop2;
  $scope.prop3 = testService.prop3;
  watch(testService, ['prop1', 'prop2'], $scope, $scope);
}

  1. ใช้ Watch obj นั้นยอดเยี่ยม แต่ก็ไม่เพียงพอหากคุณมีรหัสแบบอะซิงโครนัสในบริการของคุณ สำหรับกรณีนี้ฉันใช้ยูทิลิตีที่สองซึ่งมีลักษณะดังนี้:

mod.service('apply', ['$timeout', ApplyService]);

function ApplyService($timeout) {
  return function apply() {
    $timeout(function() {});
  };
}

ฉันจะเรียกมันในตอนท้ายของรหัส async ของฉันเพื่อเรียก $ loop loop เช่นนั้น:

app.service('TestService', ['apply', TestService]);

function TestService(apply) {
  this.apply = apply;
}
TestService.prototype.test3 = function() {
  setTimeout(function() {
    this.prop1 = 'changed_test_2';
    this.prop2 = 'changed2_test_2';
    this.prop3 = 'changed3_test_2';
    this.apply(); //trigger $digest loop
  }.bind(this));
}

ดังนั้นทั้งหมดที่อยู่ด้วยกันจะมีลักษณะเช่นนั้น (คุณสามารถเรียกใช้หรือเล่นซอเปิด ):

// TEST app code

var app = angular.module('app', ['watch_utils']);

app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);

function TestWatchCtrl($scope, testService, watch) {
  $scope.prop1 = testService.prop1;
  $scope.prop2 = testService.prop2;
  $scope.prop3 = testService.prop3;
  watch(testService, ['prop1', 'prop2'], $scope, $scope);
  $scope.test1 = function() {
    testService.test1();
  };
  $scope.test2 = function() {
    testService.test2();
  };
  $scope.test3 = function() {
    testService.test3();
  };
}

app.service('TestService', ['apply', TestService]);

function TestService(apply) {
  this.apply = apply;
  this.reset();
}
TestService.prototype.reset = function() {
  this.prop1 = 'unchenged';
  this.prop2 = 'unchenged2';
  this.prop3 = 'unchenged3';
}
TestService.prototype.test1 = function() {
  this.prop1 = 'changed_test_1';
  this.prop2 = 'changed2_test_1';
  this.prop3 = 'changed3_test_1';
}
TestService.prototype.test2 = function() {
  setTimeout(function() {
    this.prop1 = 'changed_test_2';
    this.prop2 = 'changed2_test_2';
    this.prop3 = 'changed3_test_2';
  }.bind(this));
}
TestService.prototype.test3 = function() {
  setTimeout(function() {
    this.prop1 = 'changed_test_2';
    this.prop2 = 'changed2_test_2';
    this.prop3 = 'changed3_test_2';
    this.apply();
  }.bind(this));
}
//END TEST APP CODE

//WATCH UTILS
var mod = angular.module('watch_utils', []);

mod.service('apply', ['$timeout', ApplyService]);

function ApplyService($timeout) {
  return function apply() {
    $timeout(function() {});
  };
}

mod.service('WatchObj', ['$rootScope', WatchObjService]);

function WatchObjService($rootScope) {
  // target not always equals $scope, for example when using bindToController syntax in 
  //directives
  return function watch_obj(obj, fields, target, $scope) {
    // if $scope is not provided, $rootScope is used
    $scope = $scope || $rootScope;
    var watched = fields.map(function(field) {
      return $scope.$watch(
        function() {
          return obj[field];
        },
        function(new_val) {
          target[field] = new_val;
        }
      );
    });
    var unregister = function unregister_watch_obj() {
      watched.map(function(unregister) {
        unregister();
      });
    };
    $scope.$on('$destroy', unregister);
    return unregister;
  };
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class='test' ng-app="app" ng-controller="TestWatch">
  prop1: {{prop1}}
  <br>prop2: {{prop2}}
  <br>prop3 (unwatched): {{prop3}}
  <br>
  <button ng-click="test1()">
    Simple props change
  </button>
  <button ng-click="test2()">
    Async props change
  </button>
  <button ng-click="test3()">
    Async props change with apply
  </button>
</div>

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