เรียกใช้ฟังก์ชันคอนโทรลเลอร์จากคำสั่งโดยไม่มีขอบเขตแยกใน AngularJS


95

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

<button ng-hide="hideButton()" confirm="Are you sure?" confirm-action="doIt()">Do It</button>

ในตัวอย่างง่ายๆนี้ฉันต้องการแสดงกล่องโต้ตอบยืนยัน JavaScript และเรียก doIt () เฉพาะเมื่อคลิก "ตกลง" ในกล่องโต้ตอบการยืนยัน นี่เป็นเรื่องง่ายโดยใช้ขอบเขตแยก คำสั่งจะมีลักษณะดังนี้:

.directive('confirm', function () {
    return {
        restrict: 'A',
        scope: {
            confirm: '@',
            confirmAction: '&'
        },
        link: function (scope, element, attrs) {
            element.bind('click', function (e) {
                if (confirm(scope.confirm)) {
                    scope.confirmAction();
                }
            });
        }
    };
})

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

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

นี่คือ jsfiddleที่ฉันไม่ได้ใช้ขอบเขตที่แยกออกมาและ ng-hide ทำงานได้ดี แต่แน่นอนว่าการเรียกเพื่อ confirmAction () ไม่ทำงานและฉันไม่รู้ว่าจะทำให้มันใช้งานได้อย่างไร

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

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


สำหรับผู้ที่เดินผ่านแดนวาห์ลินได้เขียนบทความที่ดีมากเพื่ออธิบายขอบเขตและพารามิเตอร์ฟังก์ชันที่แยกได้: weblogs.asp.net/dwahlin/…
tanguy_k

คำตอบ:


118

เนื่องจากคำสั่งเรียกใช้ฟังก์ชันเท่านั้น (และไม่ได้พยายามกำหนดค่าในคุณสมบัติ) คุณสามารถใช้$ evalแทน $ parse (โดยมีขอบเขตที่ไม่แยก):

scope.$apply(function() {
    scope.$eval(attrs.confirmAction);
});

หรือดีกว่าเพียงแค่ใช้$ applyซึ่งจะทำให้ $ eval () โต้แย้งกับขอบเขต:

scope.$apply(attrs.confirmAction);

ซอ


ไม่ทราบว่าขอบคุณสำหรับสิ่งนั้น ฉันคิดเสมอว่าวิธีการแยกวิเคราะห์ $ นั้นใช้คำมากเกินไป
Clark Pan

2
คุณจะตั้งค่าพารามิเตอร์ในฟังก์ชันนั้นอย่างไร
CMCDragonkai

2
@CMCDragonkai หากฟังก์ชันมีอาร์กิวเมนต์คุณสามารถระบุได้ใน HTML confirm-action="doIt(arg1)"จากนั้นตั้งค่าscope.arg1ก่อนที่จะเรียก $ eval อย่างไรก็ตามการใช้ $ parse น่าจะดีกว่า: stackoverflow.com/a/16200618/215945
Mark Rajcok

เป็นไปไม่ได้ที่จะทำขอบเขต $ eval (attrs.doIt ({arg1: 'blah'});?
CMCDragonkai

3
@CMCDragonkai ไม่scope.$eval(attrs.confirmAction({arg1: 'blah'})ไม่ทำงาน คุณจะต้องใช้ $ parse กับไวยากรณ์นั้น
Mark Rajcok

17

คุณต้องการใช้บริการ $ parse ใน AngularJS

ดังนั้นสำหรับตัวอย่างของคุณ:

.directive('confirm', function ($parse) {
    return {
        restrict: 'A',

        // Child scope, not isolated
        scope : true,
        link: function (scope, element, attrs) {
            element.bind('click', function (e) {
                if (confirm(attrs.confirm)) {
                    scope.$apply(function(){

                        // $parse returns a getter function to be 
                        // executed against an object
                        var fn = $parse(attrs.confirmAction);

                        // In our case, we want to execute the statement in 
                        // confirmAction i.e. 'doIt()' against the scope 
                        // which this directive is bound to, because the 
                        // scope is a child scope, not an isolated scope. 
                        // The prototypical inheritance of scopes will mean 
                        // that it will eventually find a 'doIt' function 
                        // bound against a parent's scope.
                        fn(scope, {$event : e});
                    });
                }
            });
        }
    };
});

มี gotcha เกี่ยวกับการตั้งค่าคุณสมบัติโดยใช้ $ parse แต่ฉันจะให้คุณข้ามสะพานนั้นเมื่อคุณมาถึงมัน


ขอบคุณคลาร์กนี่คือสิ่งที่ฉันกำลังมองหา ฉันได้ลองใช้ $ parse แล้ว แต่ไม่ผ่านขอบเขตดังนั้นจึงไม่ได้ผลสำหรับฉัน เหมาะมากครับ
Jim Cooper

ฉันประหลาดใจที่พบว่าโซลูชันนี้ใช้งานได้โดยไม่มีขอบเขต: จริง ความเข้าใจของฉันคือขอบเขต: ความจริงคือสิ่งที่ทำให้ขอบเขตของคำสั่งสืบทอดต้นแบบมาจากขอบเขตหลัก มีความคิดว่าทำไมสิ่งนี้ยังใช้งานได้โดยไม่มีมัน?
Jim Cooper

2
ไม่มีขอบเขตลูก (การลบscope:true) ทำงานได้เนื่องจากคำสั่งจะไม่สร้างขอบเขตใหม่เลยดังนั้นscopeคุณสมบัติที่คุณอ้างอิงในlinkฟังก์ชันจึงเป็นขอบเขตเดียวกันกับขอบเขต "หลัก" ก่อนหน้านี้
Clark Pan

$ ใช้ก็เพียงพอสำหรับกรณีนี้ (ดูคำตอบของฉัน)
Mark Rajcok

1
$ event มีไว้เพื่ออะไร?
CMCDragonkai

12

เรียกhideButtonขอบเขตพาเรนต์อย่างชัดเจน

นี่คือซอ: http://jsfiddle.net/pXej2/5/

และนี่คือ HTML ที่อัปเดต:

<div ng-app="myModule" ng-controller="myController">
    <input ng-model="showIt"></input>
    <button ng-hide="$parent.hideButton()" confirm="Are you sure?" confirm-action="doIt()">Do It</button>
</div>

+1 สำหรับโซลูชันที่ใช้งานได้ ฉันได้ลองใช้ $ parent แล้วและเมื่อมันไม่ได้ผลก็ยอมแพ้ หลังจากเห็นวิธีแก้ปัญหาของคุณแล้วฉันได้ตรวจสอบอย่างละเอียดและปรากฎว่าฉันไม่สามารถเพิ่ม $ parent ให้กับพารามิเตอร์ที่ฉันส่งผ่านไปได้ (ไม่ปรากฏในซอต้นฉบับของฉัน) ตัวอย่างเช่นถ้าฉันต้องการส่ง showIt ในฟังก์ชัน hideButton () ฉันจะต้องใช้ $ parent.hideButton ($ parent.showIt) และฉันไม่มี $ parent ตัวที่สอง ฉันจะยอมรับคำตอบของคลาร์กอย่างไรก็ตามเนื่องจากโซลูชันนั้นไม่ต้องการให้ผู้ใช้คำสั่งของฉันรู้ว่าพวกเขาจำเป็นต้องเพิ่ม $ parent ขอบคุณสำหรับข้อมูลเชิงลึก!
Jim Cooper

1

ปัญหาเดียวที่ฉันมีคือฉันจะเรียกเมธอดในขอบเขตพาเรนต์ได้อย่างไรหากฉันไม่ผ่านมันในขอบเขตที่แยก

นี่คือ jsfiddle ที่ฉันไม่ได้ใช้ขอบเขตที่แยกออกมาและ ng-hide ทำงานได้ดี แต่แน่นอนว่าการเรียกเพื่อ confirmAction () ไม่ทำงานและฉันไม่รู้ว่าจะทำให้มันใช้งานได้อย่างไร

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

ต่อไปการเขียนattrs.confirmAction()ไม่ทำงานเพราะattrs.confirmActionสำหรับปุ่มนี้

<button ... confirm-action="doIt()">Do It</button>

เป็นสตริงและคุณไม่สามารถเรียกสตริงเช่น"doIt()""doIt()"()

คำถามของคุณคือ:

ฉันจะเรียกใช้ฟังก์ชันได้อย่างไรเมื่อฉันมีชื่อเป็นสตริง

ใน JavaScript คุณส่วนใหญ่เรียกใช้ฟังก์ชันโดยใช้สัญลักษณ์จุดเช่นนี้:

some_obj.greet()

แต่คุณยังสามารถใช้สัญกรณ์อาร์เรย์:

some_obj['greet']

สังเกตว่าค่าดัชนีเป็นสตริงอย่างไร ดังนั้นในคำสั่งของคุณคุณสามารถทำได้ง่ายๆ:

  link: function (scope, element, attrs) {

    element.bind('click', function (e) {

      if (confirm(attrs.confirm)) {
        var func_str = attrs.confirmAction;
        var func_name = func_str.substring(0, func_str.indexOf('(')); // Or func_str.match(/[^(]+/)[0];
        func_name = func_name.trim();

        console.log(func_name); // => doIt
        scope[func_name]();
      }
    });
  }

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