Gọi một hàm điều khiển từ một chỉ thị không có phạm vi riêng biệt trong AngularJS


95

Tôi dường như không thể tìm ra cách gọi một hàm trên phạm vi chính từ bên trong một chỉ thị mà không sử dụng phạm vi bị cô lập. Tôi biết rằng nếu tôi sử dụng phạm vi bị cô lập, tôi chỉ có thể sử dụng "&" trong phạm vi cô lập để truy cập hàm trên phạm vi chính, nhưng sử dụng phạm vi cô lập khi không cần thiết sẽ dẫn đến hậu quả. Hãy xem xét HTML sau:

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

Trong ví dụ đơn giản này, tôi muốn hiển thị hộp thoại xác nhận JavaScript và chỉ gọi doIt () nếu họ nhấp vào "OK" trong hộp thoại xác nhận. Điều này đơn giản bằng cách sử dụng một phạm vi bị cô lập. Chỉ thị sẽ có dạng như sau:

.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();
                }
            });
        }
    };
})

Nhưng vấn đề là, bởi vì tôi đang sử dụng phạm vi bị cô lập, ng-hide trong ví dụ trên không còn thực thi đối với phạm vi mẹ nữa , mà là trong phạm vi cô lập (vì sử dụng phạm vi cô lập trên bất kỳ chỉ thị nào khiến tất cả các chỉ thị trên phần tử đó sử dụng phạm vi cô lập). Đây là jsFiddle của ví dụ trên mà ng-hide không hoạt động. (Lưu ý rằng trong trò chơi này, nút sẽ ẩn khi bạn nhập "có" vào hộp nhập liệu.)

Giải pháp thay thế là KHÔNG sử dụng phạm vi bị cô lập , đây thực sự là điều tôi thực sự muốn ở đây vì không cần phạm vi của chỉ thị này bị cô lập. Vấn đề duy nhất tôi gặp phải là, làm cách nào để gọi một phương thức trên phạm vi chính nếu tôi không chuyển nó vào trong phạm vi cô lập ?

Đây là một jsfiddle mà tôi KHÔNG sử dụng phạm vi cô lập và ng-hide hoạt động tốt, nhưng tất nhiên, lệnh gọi xác nhận () không hoạt động và tôi không biết làm thế nào để làm cho nó hoạt động.

Xin lưu ý, câu trả lời mà tôi thực sự đang tìm kiếm là cách gọi các hàm trên phạm vi bên ngoài mà KHÔNG sử dụng phạm vi cô lập. Và tôi không quan tâm đến việc làm cho hộp thoại xác nhận này hoạt động theo cách khác, bởi vì mục đích của câu hỏi này là tìm ra cách thực hiện lệnh gọi đến phạm vi bên ngoài và vẫn có thể có các lệnh khác hoạt động đối với phạm vi chính.

Ngoài ra, tôi muốn biết về các giải pháp sử dụng phạm vi bị cô lập nếu các chỉ thị khác vẫn hoạt động chống lại phạm vi chính , nhưng tôi không nghĩ điều này là có thể.


Đối với những người đi ngang qua, Dan Wahlin đã viết một bài viết rất tốt giải thích cô lập phạm vi và chức năng các thông số: weblogs.asp.net/dwahlin/...
tanguy_k

Câu trả lời:


117

Vì chỉ thị chỉ gọi một hàm (và không cố gắng đặt giá trị trên một thuộc tính), bạn có thể sử dụng $ eval thay vì $ parse (với phạm vi không bị cô lập):

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

Hoặc tốt hơn, chỉ cần sử dụng $ apply , điều này sẽ $ eval () bổ sung đối số của nó so với phạm vi:

scope.$apply(attrs.confirmAction);

Vĩ cầm


Không biết rằng, cảm ơn vì điều đó. Tôi luôn nghĩ rằng phương pháp $ parse hơi quá dài dòng.
Clark Pan,

2
bạn sẽ thiết lập các thông số trên hàm đó như thế nào?
CMCDragonkai

2
@CMCDragonkai, nếu hàm có các đối số, bạn có thể chỉ định chúng trong HTML, confirm-action="doIt(arg1)"rồi đặt scope.arg1trước khi gọi $ eval. Tuy nhiên, nó có lẽ sẽ sạch hơn để sử dụng $ phân tích cú pháp: stackoverflow.com/a/16200618/215945
Đánh dấu Rajcok

Có không thể thực hiện phạm vi. $ Eval (attrs.doIt ({arg1: 'blah'});?
CMCDragonkai

3
@CMCDragonkai, không, scope.$eval(attrs.confirmAction({arg1: 'blah'})sẽ không hoạt động. Bạn sẽ cần sử dụng $ parse với cú pháp đó.
Mark Rajcok

17

Bạn muốn sử dụng dịch vụ $ parse trong AngularJS.

Vì vậy, ví dụ của bạn:

.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});
                    });
                }
            });
        }
    };
});

Có một việc cần làm khi đặt thuộc tính bằng $ parse, nhưng tôi sẽ cho phép bạn vượt qua cây cầu đó khi bạn đến với nó.


Cảm ơn, Clark, đây chính xác là những gì tôi đang tìm kiếm. Tôi đã thử sử dụng $ parse, nhưng không vượt qua được phạm vi nên nó không hoạt động với tôi. Đây là hoàn hảo.
Jim Cooper

Tôi rất ngạc nhiên khi thấy rằng giải pháp này cũng hoạt động mà không cần phạm vi: true. Sự hiểu biết của tôi là phạm vi đó: true là thứ làm cho phạm vi của chỉ thị kế thừa nguyên mẫu từ phạm vi mẹ. Bất kỳ ý tưởng tại sao điều này vẫn hoạt động mà không có nó?
Jim Cooper

2
không có phạm vi con (xóa scope:true) nào hoạt động bởi vì chỉ thị sẽ không tạo phạm vi mới, và do đó thuộc scopetính bạn tham chiếu trong linkhàm là phạm vi chính xác như phạm vi 'cha' trước đó.
Clark Pan,

$ apply là đủ cho trường hợp này (xem câu trả lời của tôi).
Mark Rajcok,

1
Sự kiện $ để làm gì?
CMCDragonkai

12

Gọi rõ ràng hideButtontrên phạm vi cha.

Đây là fiddle: http://jsfiddle.net/pXej2/5/

Và đây là HTML được cập nhật:

<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 cho một giải pháp hoạt động. Tôi đã thực sự thử sử dụng $ cha mẹ và khi nó không hoạt động đã bỏ cuộc. Sau khi xem giải pháp của bạn, tôi đã xem xét kỹ hơn và hóa ra tôi đã không thêm được $ parent vào các tham số mà tôi đang chuyển vào (không được hiển thị trong fiddle ban đầu của tôi). Ví dụ: nếu tôi muốn chuyển showIt vào trong hàm hideButton (), tôi cần sử dụng $ parent.hideButton ($ parent.showIt) và tôi đã thiếu $ cha thứ hai. Tuy nhiên, tôi sẽ chấp nhận câu trả lời của Clark vì giải pháp đó không yêu cầu người dùng chỉ thị của tôi biết họ cần thêm $ cha. Cảm ơn cho cái nhìn sâu sắc!
Jim Cooper

1

Vấn đề duy nhất tôi gặp phải là, làm cách nào để gọi một phương thức trên phạm vi chính nếu tôi không chuyển nó vào trong phạm vi cô lập?

Đây là một jsfiddle mà tôi KHÔNG sử dụng phạm vi bị cô lập và ng-hide đang hoạt động tốt, nhưng tất nhiên, lệnh gọi confirmAction () không hoạt động và tôi không biết làm thế nào để làm cho nó hoạt động.

Đầu tiên, một điểm nhỏ: Trong trường hợp này, phạm vi của bộ điều khiển bên ngoài không phải là phạm vi chính của phạm vi chỉ thị; Phạm vi điều khiển bên ngoài của phạm vi của chỉ thị. Nói cách khác, tên biến được sử dụng trong chỉ thị sẽ được tra cứu trực tiếp trong phạm vi của bộ điều khiển.

Tiếp theo, tính năng viết attrs.confirmAction()không hoạt động vì attrs.confirmActionđối với nút này,

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

là một chuỗi "doIt()", và bạn không thể gọi một chuỗi, ví dụ "doIt()"().

Câu hỏi của bạn thực sự là:

Làm cách nào để gọi một hàm khi tôi có tên là một chuỗi?

Trong JavaScript, bạn chủ yếu gọi các hàm bằng ký hiệu dấu chấm, như sau:

some_obj.greet()

Nhưng bạn cũng có thể sử dụng ký hiệu mảng:

some_obj['greet']

Lưu ý cách giá trị chỉ mục là một chuỗi . Vì vậy, trong chỉ thị của bạn, bạn có thể chỉ cần thực hiện điều này:

  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 vì ý tưởng phân tích cú pháp hàm rất khó và đã giúp tôi giải quyết được vấn đề tương tự nhưng khác. Cảm ơn!
Anmol Saraf
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.