AngularJSでスコープを分離せずにディレクティブからコントローラー関数を呼び出す


95

分離スコープを使用せずに、ディレクティブ内から親スコープの関数を呼び出す方法を見つけることができないようです。分離スコープを使用する場合、分離で "&"を使用して親スコープの関数にアクセスできることを知っていますが、必要がないときに分離スコープを使用すると影響があります。次のHTMLについて考えてみます。

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

この簡単な例では、JavaScriptの確認ダイアログを表示し、確認ダイアログで[OK]をクリックした場合にのみ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は親スコープに対して実行されず、分離スコープで実行されることです(ディレクティブで分離スコープを使用すると、その要素のすべてのディレクティブが分離スコープを使用します)。以下は、 ng-hideが機能しない上記の例のjsFiddleです。(このフィドルでは、入力ボックスに「yes」と入力すると、ボタンが非表示になることに注意してください。)

代わりに、分離されたスコープを使用ないこともできます。これは、このディレクティブのスコープを分離する必要がないため、実際にここで本当に必要なものです。私が持っている唯一の問題は、分離されたスコープに渡さない場合、親スコープのメソッドをどのように呼び出すのですか?

これは、孤立スコープを使用していないng-hideのjsfiddleですが、もちろん ng-hideは正常に動作していますが、confirmAction()の呼び出しは機能せず、動作させる方法がわかりません。

注意してください、私が本当に探している答えは、分離されたスコープを使用せずに外部スコープの関数を呼び出す方法です。また、この確認のポイントは、外部スコープを呼び出す方法を理解し、それでも他のディレクティブを親スコープに対して機能させることができるためです。

または、他のディレクティブがまだ親スコープに対して機能する場合、分離されたスコープを使用するソリューションについて聞いてみたいと思いますが、これは可能ではないと思います。


:渡し方のために、ダンWahlinは、分離株のスコープと機能パラメータ説明非常に良い記事を書いていweblogs.asp.net/dwahlin/...
tanguy_kを

回答:


117

ディレクティブは関数を呼び出すだけで(プロパティに値を設定しようとはしない)、$ parseの代わりに$ evalを使用できます(分離されていないスコープを使用)。

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

または、単に$ applyを使用するだけで、スコープに対して引数を$ eval()uateできます。

scope.$apply(attrs.confirmAction);

フィドル


それを知りませんでした、ありがとう。$ parseメソッドは少し冗長すぎるといつも思っていました。
クラークパン

2
その関数のパラメーターをどのように設定しますか?
CMCDragonkai 2013

2
@CMCDragonkai、関数に引数がある場合、HTMLでそれらを指定し、$ evalを呼び出す前にconfirm-action="doIt(arg1)"設定できますscope.arg1。しかし、おそらく$解析を使用するためにきれいになります:stackoverflow.com/a/16200618/215945
マーク・Rajcok

scope。$ eval(attrs.doIt({arg1: 'blah'});?を実行することはできませんか?
CMCDragonkai

3
@CMCDragonkai、機能しませscope.$eval(attrs.confirmAction({arg1: 'blah'})ん。その構文で$ parseを使用する必要があります。
Mark Rajcok 2013

17

AngularJSで$ parseサービスを利用したいとします。

だからあなたの例では:

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

$ parseを使用してプロパティを設定する際の問題はありますが、それに到達したら、そのブリッジを渡らせます。


おかげで、クラーク、これはまさに私が探していたものです。$ parseを使用しようとしましたが、スコープを渡すことができなかったため、機能しませんでした。これは完璧です。
ジムクーパー

このソリューションもscope:trueなしでも機能することを知って驚いた。私の理解では、scope:trueはディレクティブのスコープをプロトタイプ的に親スコープから継承するものです。これがなぜそれなしでまだ機能するのか、何か考えはありますか?
ジムクーパー

2
scope:trueディレクティブは新しいスコープをまったく作成しないため、子スコープ(削除)scopelink機能しません。したがって、関数で参照するプロパティは、以前の「親」スコープとまったく同じスコープです。
クラークパン

この場合は$ applyで十分です(私の回答を参照してください)。
Mark Rajcok 2013

1
$ eventは何のためのものですか?
CMCDragonkai 2013

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)を使用する必要があり、2番目の$ parentがありませんでした。ただし、このソリューションでは、ディレクティブのユーザーが$ parentを追加する必要があることを知る必要がないため、クラークの回答を受け入れます。洞察をありがとう!
ジムクーパー

1

私が抱えている唯一の問題は、分離されたスコープで渡さない場合、親スコープでメソッドを呼び出すにはどうすればよいですか?

以下は、孤立したスコープを使用していないng-hideが正常に動作しているjsfiddleですが、もちろん、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.