AngularJS: วิธีที่ถูกต้องในการเชื่อมโยงกับคุณสมบัติของบริการ


162

ฉันกำลังมองหาวิธีปฏิบัติที่ดีที่สุดในการเชื่อมโยงกับคุณสมบัติบริการใน AngularJS

ฉันได้ทำงานผ่านหลายตัวอย่างเพื่อเข้าใจวิธีเชื่อมโยงกับคุณสมบัติในบริการที่สร้างขึ้นโดยใช้ AngularJS

ด้านล่างฉันมีสองตัวอย่างของวิธีการเชื่อมโยงกับคุณสมบัติในบริการ พวกเขาทั้งสองทำงาน ตัวอย่างแรกใช้การเชื่อมโยงพื้นฐานและตัวอย่างที่สองใช้ $ scope $ watch เพื่อเชื่อมโยงกับคุณสมบัติของบริการ

ตัวอย่างเหล่านี้เป็นที่ต้องการเมื่อเชื่อมโยงกับคุณสมบัติในบริการหรือมีตัวเลือกอื่นที่ฉันไม่ทราบว่าจะแนะนำหรือไม่

หลักฐานของตัวอย่างเหล่านี้คือบริการควรอัปเดตคุณสมบัติ "lastUpdated" และ "การโทร" ทุก 5 วินาที เมื่ออัปเดตคุณสมบัติของบริการแล้วมุมมองควรสะท้อนถึงการเปลี่ยนแปลงเหล่านี้ ตัวอย่างทั้งสองนี้ทำงานได้สำเร็จ ฉันสงสัยว่ามีวิธีที่ดีกว่าในการทำมัน

การผูกแบบพื้นฐาน

รหัสต่อไปนี้สามารถดูและวิ่งได้ที่นี่: http://plnkr.co/edit/d3c16z

<html>
<body ng-app="ServiceNotification" >

    <div ng-controller="TimerCtrl1" style="border-style:dotted"> 
        TimerCtrl1 <br/>
        Last Updated: {{timerData.lastUpdated}}<br/>
        Last Updated: {{timerData.calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.timerData = Timer.data;
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 5000);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>
</html>

วิธีอื่นที่ฉันแก้ไขการเชื่อมโยงกับคุณสมบัติของบริการคือการใช้ $ scope $ watch ในคอนโทรลเลอร์

$ ขอบเขต. $ นาฬิกา

รหัสต่อไปนี้สามารถดูและวิ่งได้ที่นี่: http://plnkr.co/edit/dSBlC9

<html>
<body ng-app="ServiceNotification">
    <div style="border-style:dotted" ng-controller="TimerCtrl1">
        TimerCtrl1<br/>
        Last Updated: {{lastUpdated}}<br/>
        Last Updated: {{calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.$watch(function () { return Timer.data.lastUpdated; },
                function (value) {
                    console.log("In $watch - lastUpdated:" + value);
                    $scope.lastUpdated = value;
                }
            );

            $scope.$watch(function () { return Timer.data.calls; },
                function (value) {
                    console.log("In $watch - calls:" + value);
                    $scope.calls = value;
                }
            );
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 5000);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>
</html>

ฉันทราบว่าฉันสามารถใช้ $ rootcope $ broadcast ในบริการและ $ root $ $ ในคอนโทรลเลอร์ แต่ในตัวอย่างอื่น ๆ ที่ฉันสร้างขึ้นโดยใช้ $ broadcast / $ เมื่อออกอากาศครั้งแรกไม่ได้ถูกจับ คอนโทรลเลอร์ แต่การโทรเพิ่มเติมที่ออกอากาศจะถูกเรียกใช้ในคอนโทรลเลอร์ หากคุณตระหนักถึงวิธีการแก้ $ rootcope ปัญหาการออกอากาศ $ โปรดระบุคำตอบ

แต่เพื่อย้ำสิ่งที่ฉันพูดถึงก่อนหน้านี้ฉันต้องการทราบวิธีปฏิบัติที่ดีที่สุดของการผูกกับคุณสมบัติของบริการ


ปรับปรุง

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


ฉันคิดว่าคำตอบของ Josh David Miller ดีกว่าของ Gil Birman และการใช้ $ watch, $ watchGroup และ $ watchCollection สามารถทำให้ดียิ่งขึ้นได้ การแยกข้อกังวลมีความสำคัญมากสำหรับแอปขนาดกลางถึงขนาดใหญ่
Jonathan

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

ปัญหาที่คุณถามเกี่ยวกับพฤติกรรมของตัวแปรจาวาสคริปต์ในการอ้างอิงอ็อบเจกต์ฉันเพิ่มคำอธิบายเล็กน้อยด้านล่าง
Zalaboza

คำตอบ:


100

พิจารณาข้อดีข้อเสียของแนวทางที่สอง :

  • 0 {{lastUpdated}}แทนที่จะ{{timerData.lastUpdated}}เป็นได้อย่างง่ายดาย{{timer.lastUpdated}}ซึ่งฉันอาจโต้เถียงอ่านง่ายกว่า (แต่อย่าเถียง ... ฉันให้คะแนนนี้ในระดับที่เป็นกลางเพื่อให้คุณตัดสินใจเอง)

  • +1มันอาจจะสะดวกที่ตัวควบคุมทำหน้าที่เหมือนกับ API สำหรับมาร์กอัพเช่นถ้าโครงสร้างของแบบจำลองข้อมูลเปลี่ยนแปลงคุณสามารถ (ในทางทฤษฎี) ปรับปรุงการแมป APIของตัวควบคุมโดยไม่ต้องสัมผัส HTML บางส่วน

  • -1อย่างไรก็ตามทฤษฎีไม่เคยปฏิบัติและฉันมักจะพบว่าตัวเองมีการปรับเปลี่ยนมาร์กอัปและควบคุมตรรกะเมื่อมีการเปลี่ยนแปลงที่เรียกว่าหาอยู่แล้ว ดังนั้นความพยายามพิเศษในการเขียน API จึงเป็นข้อได้เปรียบ

  • -1นอกจากนี้วิธีนี้ยังไม่แห้งมาก

  • -1ถ้าคุณต้องการผูกข้อมูลลงในng-modelโค้ดของคุณจะกลายเป็น DRY ที่น้อยลงเนื่องจากคุณต้องทำการแพคเกจใหม่$scope.scalar_valuesในคอนโทรลเลอร์เพื่อทำการเรียกใช้ REST ใหม่

  • -0.1มีการใช้งานน้อยมากในการสร้างนักดูพิเศษ นอกจากนี้หากมีการแนบคุณสมบัติของข้อมูลกับรุ่นที่ไม่จำเป็นต้องดูในตัวควบคุมเฉพาะพวกเขาจะสร้างค่าใช้จ่ายเพิ่มเติมสำหรับนักดูลึก

  • -1จะเกิดอะไรขึ้นถ้าคอนโทรลเลอร์หลายตัวต้องการโมเดลข้อมูลเดียวกัน ซึ่งหมายความว่าคุณมี API หลายตัวที่จะต้องอัปเดตทุกครั้งที่มีการเปลี่ยนแปลง

$scope.timerData = Timer.data;กำลังเริ่มฟังดูเย้ายวนยิ่งใหญ่ในตอนนี้ ... ลองดำดิ่งลงไปในจุดสุดท้ายนั่นหน่อย ... เรากำลังพูดถึงการเปลี่ยนแปลงรูปแบบอะไร? รูปแบบในส่วนท้าย (เซิร์ฟเวอร์)? หรือรูปแบบที่ถูกสร้างขึ้นและมีชีวิตอยู่ในส่วนหน้าเท่านั้น? ไม่ว่าในกรณีใดก็ตามสิ่งที่สำคัญคือAPI การแมปข้อมูลอยู่ในเลเยอร์บริการส่วนหน้า (โรงงานเชิงมุมหรือบริการ) (โปรดทราบว่าตัวอย่างแรกของคุณ - การตั้งค่าของฉัน - ไม่มี API ดังกล่าวในชั้นบริการซึ่งใช้ได้เพราะมันง่ายพอที่ไม่ต้องการมัน)

โดยสรุปแล้วทุกอย่างไม่จำเป็นต้องแยกจากกัน และเท่าที่การแยกมาร์กอัปทั้งหมดออกจากตัวแบบข้อมูลข้อเสียนั้นมีมากกว่าข้อดี


ควบคุมโดยทั่วไปไม่ควรที่เกลื่อนไปด้วย$scope = injectable.data.scalar's แต่ควรโรยด้วย$scope = injectable.data's promise.then(..)' และ$scope.complexClickAction = function() {..}'s

ในฐานะที่เป็นวิธีทางเลือกเพื่อให้บรรลุข้อมูล decoupling และทำให้มุมมอง-encapsulation, สถานที่เดียวที่เป็นเรื่องที่ทำให้ความรู้สึกที่จะแยกมุมมองจากรูปแบบคือมีคำสั่ง แต่ถึงอย่างนั้นอย่าใช้$watchค่าสเกลาร์ในcontrollerหรือlinkฟังก์ชัน ที่จะไม่ประหยัดเวลาหรือทำให้รหัสใด ๆ ที่บำรุงรักษาหรืออ่านได้มากขึ้น มันจะไม่ได้ทำให้การทดสอบง่ายตั้งแต่การทดสอบประสิทธิภาพในมุมมักจะทดสอบ DOM ที่เกิดขึ้นอยู่แล้ว แต่ในความต้องการของคุณสั่งAPI ข้อมูลในรูปแบบของวัตถุและความโปรดปรานโดยใช้เพียง$watchERS ng-bindสร้างขึ้นโดย


ตัวอย่าง http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio

<body ng-app="ServiceNotification">
    <div style="border-style:dotted" ng-controller="TimerCtrl1">
        TimerCtrl1<br/>
        Bad:<br/>
        Last Updated: {{lastUpdated}}<br/>
        Last Updated: {{calls}}<br/>
        Good:<br/>
        Last Updated: {{data.lastUpdated}}<br/>
        Last Updated: {{data.calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.data = Timer.data;
            $scope.lastUpdated = Timer.data.lastUpdated;
            $scope.calls = Timer.data.calls;
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 500);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>

UPDATE : ในที่สุดฉันก็กลับมาที่คำถามนี้เพื่อเพิ่มว่าฉันไม่คิดว่าวิธีการใดวิธีการหนึ่งนั้น "ผิด" ในขั้นต้นฉันได้เขียนว่าคำตอบของ Josh David Miller นั้นไม่ถูกต้อง แต่เมื่อมองย้อนกลับไปว่าประเด็นของเขานั้นใช้ได้อย่างสมบูรณ์

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

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

  1. การแยกข้อกังวล : ส่วนประกอบใช้ข้อมูลทั้งหมดผ่านทางอุปกรณ์ประกอบฉากและไม่มีการพึ่งพา singletons ทั่วโลกเพียงเล็กน้อย (เช่นบริการเชิงมุม) และไม่รู้อะไรเลยเกี่ยวกับสิ่งที่เกิดขึ้นข้างต้นในมุมมองลำดับชั้น

  2. ความไม่แน่นอน : อุปกรณ์ประกอบฉากทั้งหมดไม่สามารถเปลี่ยนแปลงได้ซึ่งช่วยลดความเสี่ยงของการเปลี่ยนแปลงข้อมูลโดยไม่เจตนา

Angular 2.0 กำลังเข้ามาขอยืมอย่างหนักจาก React เพื่อให้บรรลุสองจุดดังกล่าว


1
ยิ่งฉันทำงานกับ AngularJS มากเท่าไหร่ฉันก็ยิ่งเข้าใจ ฉันเชื่อว่าตัวควบคุม AngularJS ควรเรียบง่ายและบางที่สุด โดยการเพิ่ม $ watches ในตัวควบคุมมันจะทำให้ตรรกะซับซ้อน โดยการอ้างอิงค่าในบริการมันง่ายกว่ามากและดูเหมือนจะเป็นวิธีที่ AngularJS มากขึ้น -
Mike Barlow - BarDev

3
"บัญญัติสิบประการ" ของ AngularJS มันเป็นเหตุผลสำหรับการประกาศ
pilau

คำตอบของ Josh David Miller ไม่ใช่ "INCORRECT" มีเวลาและสถานที่สำหรับทุกสิ่ง
JoshuaDavid

ฉันคิดว่าคุณพูดถูกแล้ว @FireCoding ฉันวางแผนที่จะอัพเดทคำตอบ
Gil Birman

@GilBirman คำตอบที่ยอดเยี่ยม คุณจะคิดการเขียนและตัวอย่างสำหรับคำสั่งหรือไม่ เพื่อแสดงให้เห็นถึงสิ่งที่คุณพูดว่า "สถานที่เดียวที่เหมาะสมที่จะแยกมุมมองจากแบบจำลองนั้นคือด้วยคำสั่ง [... ] ค่อนข้างในคำสั่งต้องการ API ข้อมูลของคุณในรูปแบบวัตถุและสนับสนุนโดยใช้เพียง $ ผู้เฝ้าดูสร้างโดย ng-bind "
klode

78

จากมุมมองของฉัน$watchจะเป็นวิธีปฏิบัติที่ดีที่สุด

คุณสามารถทำให้ตัวอย่างของคุณง่ายขึ้นเล็กน้อย:

function TimerCtrl1($scope, Timer) {
  $scope.$watch( function () { return Timer.data; }, function (data) {
    $scope.lastUpdated = data.lastUpdated;
    $scope.calls = data.calls;
  }, true);
}

นั่นคือทั้งหมดที่คุณต้องการ

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


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

เพื่อความเป็นธรรมอาจมีหลายมุมมองที่นี่และฉันหวังว่าจะได้คำตอบอื่น ๆ


3
คุณอธิบายรายละเอียดเล็กน้อยได้ไหม? คุณชอบนาฬิกา $ เพราะมุมมองน้อยกว่าคู่กับบริการหรือไม่ Ie, {{lastUpdated}}vs.{{timerData.lastUpdated}}
Mark Rajcok

2
@BarDev เพื่อใช้Timer.dataใน $ watch Timerจะต้องมีการกำหนดในขอบเขต $ เนื่องจากนิพจน์สตริงที่คุณส่งไปยัง $ watch จะถูกประเมินเทียบกับขอบเขต นี่คือพลั่วเกอร์ที่แสดงวิธีการทำงาน พารามิเตอร์ objectEquality มีการบันทึกไว้ที่นี่ - พารามิเตอร์ที่ 3 - แต่ก็ไม่ได้อธิบายได้ดีนัก
Mark Rajcok

2
ประสิทธิภาพการทำงานที่ชาญฉลาด$watchค่อนข้างไม่มีประสิทธิภาพ ดูคำตอบได้ที่stackoverflow.com/a/17558885/932632และstackoverflow.com/questions/12576798/…
Krym

11
@Kyrm นั้นอาจเป็นจริงในบางสถานการณ์ แต่เมื่อต้องจัดการกับประสิทธิภาพเราต้องมองหาการปรับปรุงประสิทธิภาพ "ที่มีนัยสำคัญทางคลินิก" มากกว่าที่จะเป็นนัยสำคัญทางสถิติ หากมีปัญหาด้านประสิทธิภาพในแอปพลิเคชั่นที่มีอยู่ก็ควรแก้ไข มิฉะนั้นเป็นกรณีของการปรับให้เหมาะสมก่อนกำหนดซึ่งนำไปสู่โค้ดที่อ่านยากและมีข้อบกพร่องที่ไม่เป็นไปตามแนวทางปฏิบัติที่ดีที่สุดและไม่มีประโยชน์ใด ๆ
Josh David Miller

1
สมมติว่าผู้เฝ้าดูใช้ฟังก์ชั่นในการเรียกตัวรับสัญญาณแล้วใช่ มันใช้งานได้ดี ฉันยังเป็นผู้สนับสนุนของบริการที่ส่งคืนอินสแตนซ์ในคอลเลกชันซึ่งคุณสมบัติ getter และ setter ของ es5 ค่อนข้างดีแทน
Josh David Miller

19

ไปงานเลี้ยงสาย แต่สำหรับชาว Google ในอนาคต - อย่าใช้คำตอบที่ให้ไว้

JavaScript มีกลไกการส่งวัตถุโดยอ้างอิงในขณะที่มันจะส่งสำเนาตื้นสำหรับค่า "ตัวเลขสตริง ฯลฯ " เท่านั้น

ในตัวอย่างด้านบนแทนที่จะผูกคุณสมบัติของบริการทำไมเราไม่เปิดเผยบริการในขอบเขต

$scope.hello = HelloService;

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

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

เคล็ดลับสุดท้าย: ถ้าคุณใช้เวลาทำงานกับคอนโทรลเลอร์มากกว่าบริการของคุณคุณจะทำผิด :(

ในรหัสตัวอย่างที่คุณให้มาฉันขอแนะนำให้คุณทำ:

 function TimerCtrl1($scope, Timer) {
   $scope.timer = Timer;
 }
///Inside view
{{ timer.time_updated }}
{{ timer.other_property }}
etc...

แก้ไข:

ดังที่ฉันได้กล่าวไว้ข้างต้นคุณสามารถควบคุมพฤติกรรมของแอตทริบิวต์บริการของคุณโดยใช้ defineProperty

ตัวอย่าง:

// Lets expose a property named "propertyWithSetter" on our service
// and hook a setter function that automatically saves new value to db !
Object.defineProperty(self, 'propertyWithSetter', {
  get: function() { return self.data.variable; },
  set: function(newValue) { 
         self.data.variable = newValue; 
         // let's update the database too to reflect changes in data-model !
         self.updateDatabaseWithNewData(data);
       },
  enumerable: true,
  configurable: true
});

ตอนนี้ในตัวควบคุมของเราถ้าเราทำ

$scope.hello = HelloService;
$scope.hello.propertyWithSetter = 'NEW VALUE';

บริการของเราจะเปลี่ยนค่าของpropertyWithSetterและโพสต์ค่าใหม่ไปยังฐานข้อมูลอย่างใด!

หรือเราสามารถใช้วิธีการใด ๆ ที่เราต้องการ

อ้างถึงเอกสาร MDNdefinePropertyสำหรับ


ค่อนข้างแน่ใจว่านั่นคือสิ่งที่ฉันอธิบายข้างต้นด้วย$scope.model = {timerData: Timer.data};เพียงแค่แนบกับแบบจำลองแทนที่จะเป็นขอบเขตโดยตรง
Scott Silvi

1
AFAIK การอ้างอิงวัตถุ js ใช้ได้กับทุกสิ่งในบริการ การเข้ารหัสเช่นนี้: $ scope.controllerVar = ServiceVar ทุกอย่างที่คุณทำใน $ scope.controllerVar จะทำใน ServiceVar เช่นกัน
Kai Wang

@KaiWang เห็นด้วยเว้นแต่คุณตัดสินใจใช้ DefineAttribute คุณสามารถทำให้บริการของคุณมีฟังก์ชั่น setter เพื่อป้องกันไม่ให้ผู้ควบคุมไม่ให้ทำการเปลี่ยนแปลงข้อมูลบริการของคุณ
Zalaboza

12

ฉันคิดว่าคำถามนี้มีองค์ประกอบตามบริบท

หากคุณเพียงแค่ดึงข้อมูลจากบริการ & เผยแพร่ข้อมูลนั้นไปยังมุมมองของมันฉันคิดว่าการเชื่อมโยงโดยตรงกับคุณสมบัติบริการนั้นใช้ได้ ฉันไม่ต้องการเขียนโค้ดสำเร็จรูปจำนวนมากเพื่อแมปคุณสมบัติของบริการกับคุณสมบัติของโมเดลเพื่อใช้ในมุมมองของฉัน

นอกจากนี้ประสิทธิภาพในเชิงมุมขึ้นอยู่กับสองสิ่ง วิธีแรกคือจำนวนการเชื่อมโยงบนหน้าหนึ่ง ๆ อย่างที่สองก็คือฟังก์ชั่นทะเยอทะยานที่มีราคาแพง Misko พูดถึงสิ่งนี้ที่นี่

หากคุณต้องการดำเนินการตรรกะเฉพาะของอินสแตนซ์ในข้อมูลบริการ (ซึ่งตรงข้ามกับการนวดข้อมูลที่ใช้ภายในบริการเอง) และผลลัพธ์ของสิ่งนี้ส่งผลกระทบต่อรูปแบบข้อมูลที่เปิดเผยต่อมุมมองนั้นฉันจะบอกว่า ตราบใดที่ฟังก์ชั่นไม่แพงอย่างน่ากลัว ในกรณีของฟังก์ชั่นที่มีราคาแพงฉันขอแนะนำให้แคชผลลัพธ์ในตัวแปร local (to controller) ทำการดำเนินการที่ซับซ้อนของคุณนอกฟังก์ชั่น $ watcher และจากนั้นผูกขอบเขตของคุณกับผลลัพธ์นั้น

ในฐานะที่เป็นข้อแม้คุณไม่ควรแขวนคุณสมบัติใด ๆโดยตรงจากขอบเขต $ ของคุณ $scopeตัวแปรไม่ได้เป็นรูปแบบของคุณ มันมีการอ้างอิงถึงรูปแบบของคุณ

ในใจของฉัน "วิธีปฏิบัติที่ดีที่สุด" สำหรับการส่งต่อข้อมูลจากบริการลงไปเพื่อดู:

function TimerCtrl1($scope, Timer) {
  $scope.model = {timerData: Timer.data};
};

{{model.timerData.lastupdated}}และแล้วมุมมองของคุณจะมี


ระวังด้วยคำแนะนำนั้นบางคนที่ไม่เชี่ยวชาญใน javascript อาจลองทำด้วยคุณสมบัติที่เป็นสตริง ในกรณีที่จาวาสคริปต์ไม่ได้ทำการอ้างอิงถึงวัตถุ แต่ raw คัดลอกสตริง (และนั่นเป็นสาเหตุที่ไม่ดีที่จะไม่ได้รับการอัปเดตหากมีการเปลี่ยนแปลง)
Valerio

7
ฉันไม่ได้ครอบคลุมด้วย 'ข้อแม้' ของฉันที่คุณควรใช้จุด (หมายถึงไม่แขวนออก $ scope แต่ปิด $ scope.model) หากคุณมี $ scope.model.someStringProperty และคุณอ้างอิง model.someStringProperty ในมุมมองของคุณมันจะได้รับการอัปเดตเนื่องจากผู้เฝ้าดูภายในจะอยู่บนวัตถุไม่ใช่เสา
Scott Silvi

6

การสร้างตัวอย่างข้างต้นฉันคิดว่าฉันจะใส่วิธีควบคุมตัวแปรคอนโทรลเลอร์เข้ากับตัวแปรบริการอย่างโปร่งใส

ในตัวอย่างด้านล่างการเปลี่ยนแปลง$scope.countตัวแปรController จะสะท้อนให้เห็นโดยอัตโนมัติในcountตัวแปรService

ในการผลิตเราใช้การรวมนี้เพื่ออัปเดต id บนบริการซึ่งจะดึงข้อมูลและอัปเดตบริการ vars แบบอะซิงโครนัส การเชื่อมโยงเพิ่มเติมซึ่งหมายความว่าตัวควบคุมจะได้รับการอัปเดตโดยอัตโนมัติเมื่อบริการอัปเดตตัวเอง

รหัสด้านล่างสามารถมองเห็นการทำงานที่http://jsfiddle.net/xuUHS/163/

ดู:

<div ng-controller="ServiceCtrl">
    <p> This is my countService variable : {{count}}</p>
    <input type="number" ng-model="count">
    <p> This is my updated after click variable : {{countS}}</p>

    <button ng-click="clickC()" >Controller ++ </button>
    <button ng-click="chkC()" >Check Controller Count</button>
    </br>

    <button ng-click="clickS()" >Service ++ </button>
    <button ng-click="chkS()" >Check Service Count</button>
</div>

บริการ / ควบคุม:

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

app.service('testService', function(){
    var count = 10;

    function incrementCount() {
      count++;
      return count;
    };

    function getCount() { return count; }

    return {
        get count() { return count },
        set count(val) {
            count = val;
        },
        getCount: getCount,
        incrementCount: incrementCount
    }

});

function ServiceCtrl($scope, testService)
{

    Object.defineProperty($scope, 'count', {
        get: function() { return testService.count; },
        set: function(val) { testService.count = val; },
    });

    $scope.clickC = function () {
       $scope.count++;
    };
    $scope.chkC = function () {
        alert($scope.count);
    };

    $scope.clickS = function () {
       ++testService.count;
    };
    $scope.chkS = function () {
        alert(testService.count);
    };

}

นี้เป็นทางออกที่น่ากลัวขอบคุณนี้ช่วยให้ผมมากวิธีที่ชาญฉลาดมากในการทำมัน :)
Javis เปเรซ

2

ฉันคิดว่ามันเป็นวิธีที่ดีกว่าในการผูกมัดตัวบริการแทนที่จะเป็นคุณสมบัติ

นี่คือเหตุผล:

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.7/angular.min.js"></script>
<body ng-app="BindToService">

  <div ng-controller="BindToServiceCtrl as ctrl">
    ArrService.arrOne: <span ng-repeat="v in ArrService.arrOne">{{v}}</span>
    <br />
    ArrService.arrTwo: <span ng-repeat="v in ArrService.arrTwo">{{v}}</span>
    <br />
    <br />
    <!-- This is empty since $scope.arrOne never changes -->
    arrOne: <span ng-repeat="v in arrOne">{{v}}</span>
    <br />
    <!-- This is not empty since $scope.arrTwo === ArrService.arrTwo -->
    <!-- Both of them point the memory space modified by the `push` function below -->
    arrTwo: <span ng-repeat="v in arrTwo">{{v}}</span>
  </div>

  <script type="text/javascript">
    var app = angular.module("BindToService", []);

    app.controller("BindToServiceCtrl", function ($scope, ArrService) {
      $scope.ArrService = ArrService;
      $scope.arrOne = ArrService.arrOne;
      $scope.arrTwo = ArrService.arrTwo;
    });

    app.service("ArrService", function ($interval) {
      var that = this,
          i = 0;
      this.arrOne = [];
      that.arrTwo = [];

      $interval(function () {
        // This will change arrOne (the pointer).
        // However, $scope.arrOne is still same as the original arrOne.
        that.arrOne = that.arrOne.concat([i]);

        // This line changes the memory block pointed by arrTwo.
        // And arrTwo (the pointer) itself never changes.
        that.arrTwo.push(i);
        i += 1;
      }, 1000);

    });
  </script>
</body> 

คุณสามารถเล่นมันได้ในเพเกอร์เกอร์นี้


1

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


0

การผูกข้อมูลใด ๆ ที่ส่งบริการไม่ใช่ความคิดที่ดี (สถาปัตยกรรม) แต่ถ้าคุณต้องการอีกต่อไปฉันแนะนำให้คุณทำ 2 วิธี

1) คุณสามารถรับข้อมูลที่ไม่ได้อยู่ในบริการของคุณคุณสามารถรับข้อมูลภายในตัวควบคุม / คำสั่งของคุณและคุณจะไม่มีปัญหาในการผูกไว้ที่ใดก็ได้

2) คุณสามารถใช้เหตุการณ์ angularjs เมื่อใดก็ตามที่คุณต้องการคุณสามารถส่งสัญญาณ (จาก $ rootScope) และจับมันทุกที่ที่คุณต้องการคุณยังสามารถส่งข้อมูลใน eventName นั้นได้

บางทีนี่อาจช่วยคุณได้ หากคุณต้องการมากขึ้นด้วยตัวอย่างนี่คือลิงค์

http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html



-2

ทางออกที่ดีที่สุด ...

app.service('svc', function(){ this.attr = []; return this; });
app.controller('ctrl', function($scope, svc){
    $scope.attr = svc.attr || [];
    $scope.$watch('attr', function(neo, old){ /* if necessary */ });
});
app.run(function($rootScope, svc){
    $rootScope.svc = svc;
    $rootScope.$watch('svc', function(neo, old){ /* change the world */ });
});

นอกจากนี้ฉันเขียน EDAs (สถาปัตยกรรมที่ขับเคลื่อนด้วยเหตุการณ์) ดังนั้นฉันจึงมักจะทำสิ่งต่อไปนี้ [เวอร์ชั่นที่มีขนาดใหญ่เกินไป]:

var Service = function Service($rootScope) {
    var $scope = $rootScope.$new(this);
    $scope.that = [];
    $scope.$watch('that', thatObserver, true);
    function thatObserver(what) {
        $scope.$broadcast('that:changed', what);
    }
};

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

โดยสรุปมีไม่มาก "วิธีปฏิบัติที่ดีที่สุด" - แต่ส่วนใหญ่จะเป็นความชอบ - ตราบใดที่คุณยังคงมีสิ่งที่เป็นของแข็งและใช้ข้อต่อที่อ่อนแอ เหตุผลที่ฉันจะสนับสนุนรหัสหลังนั้นเป็นเพราะ EDAs มีเพศสัมพันธ์ต่ำที่สุดที่เป็นไปได้โดยธรรมชาติ และถ้าคุณไม่กังวลเกี่ยวกับข้อเท็จจริงนี้ให้เราหลีกเลี่ยงการทำงานในโครงการเดียวกันด้วยกัน

หวังว่านี่จะช่วย ...

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