他のコントローラーからディレクティブコントローラーのメソッドを呼び出す


118

独自のコントローラーを持つディレクティブがあります。以下のコードを参照してください:

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

popdown.directive('popdown', function () {
    var PopdownController = function ($scope) {
        this.scope = $scope;
    }

    PopdownController.prototype = {
        show:function (message, type) {
            this.scope.message = message;
            this.scope.type = type;
        },

        hide:function () {
            this.scope.message = '';
            this.scope.type = '';
        }
    }

    var linkFn = function (scope, lElement, attrs, controller) {

    };

    return {
        controller: PopdownController,
        link: linkFn,
        replace: true,
        templateUrl: './partials/modules/popdown.html'
    }

});

これは、エラー/通知/警告の通知システムであることを意図しています。私がやりたいのは、showこのコントローラの関数を呼び出すための(ディレクティブのコントローラではなく)別のコントローラからです。そして、そのとき、いくつかのプロパティが変更されたことを検出し、いくつかのアニメーションを実行するために、リンク機能も必要です。

ここに私が求めていることを例示するいくつかのコードがあります:

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

app.controller('IndexController', function($scope, RestService) {
    var result = RestService.query();

    if(result.error) {
        popdown.notify(error.message, 'error');
    }
});

したがってshowpopdownディレクティブコントローラーを呼び出すと、リンク関数もトリガーされ、アニメーションを実行する必要があります。どうすればそれを達成できますか?


popdownディレクティブの呼び出しをページのどこに配置しますか?他のコントローラーがすべてアクセスできるはずの1つの場所だけですか、それともさまざまな場所にいくつかのポップアップがありますか?
satchmorun 2013

私のindex.htmlにはこれがあります:<div ng-view> </ div> <div popdown> </ div>基本的にグローバルに利用できるように意図されているので、1つのpopdownインスタンスしかありません。
user253530

1
popdown.show(...)代わりに書くつもりだったと思いますよpopdown.notify(...)ね?それ以外の場合、通知機能は少し混乱します。
lanoxx 2014

どこから来たのpopdown.notify.notifiyメソッド、
グリーン

回答:


167

これは興味深い質問であり、私はこのようなものをどのように実装するかについて考え始めました。

私はこれを思いつきました(フィドル)

基本的に、コントローラーからディレクティブを呼び出そうとする代わりに、すべてのポップダウンロジックを格納するモジュールを作成しました。

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

モジュールには2つの要素を配置します。1つはfactoryどこにでも挿入できるAPIで、もう1つdirectiveは実際のポップダウン要素の動作を定義します。

ファクトリーはいくつかの関数successを定義し、いくつerrorかの変数を追跡します。

PopdownModule.factory('PopdownAPI', function() {
    return {
        status: null,
        message: null,
        success: function(msg) {
            this.status = 'success';
            this.message = msg;
        },
        error: function(msg) {
            this.status = 'error';
            this.message = msg;
        },
        clear: function() {
            this.status = null;
            this.message = null;
        }
    }
});

ディレクティブはコントローラーに注入されたAPIを取得し、APIの変更を監視します(便宜上、bootstrap cssを使用しています)。

PopdownModule.directive('popdown', function() {
    return {
        restrict: 'E',
        scope: {},
        replace: true,
        controller: function($scope, PopdownAPI) {
            $scope.show = false;
            $scope.api = PopdownAPI;

            $scope.$watch('api.status', toggledisplay)
            $scope.$watch('api.message', toggledisplay)

            $scope.hide = function() {
                $scope.show = false;
                $scope.api.clear();
            };

            function toggledisplay() {
                $scope.show = !!($scope.api.status && $scope.api.message);               
            }
        },
        template: '<div class="alert alert-{{api.status}}" ng-show="show">' +
                  '  <button type="button" class="close" ng-click="hide()">&times;</button>' +
                  '  {{api.message}}' +
                  '</div>'
    }
})

次にapp、以下に依存するモジュールを定義しますPopdown

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

app.controller('main', function($scope, PopdownAPI) {
    $scope.success = function(msg) { PopdownAPI.success(msg); }
    $scope.error   = function(msg) { PopdownAPI.error(msg); }
});

そして、HTMLは次のようになります。

<html ng-app="app">
    <body ng-controller="main">
        <popdown></popdown>
        <a class="btn" ng-click="success('I am a success!')">Succeed</a>
        <a class="btn" ng-click="error('Alas, I am a failure!')">Fail</a>
    </body>
</html>

それが完全に理想的かどうかはわかりませんが、グローバルなポップダウンディレクティブとの通信を設定するための合理的な方法のように思えました。

再び、参考のために、フィドル


10
+1ディレクティブの外部からディレクティブの関数を呼び出すことはできません。これは悪い習慣です。サービスを使用してディレクティブが読み取るグローバル状態を管理することは非常に一般的であり、これが正しいアプローチです。その他のアプリケーションには、通知キューとモーダルダイアログが含まれます。
Josh David Miller

7
本当に素晴らしい答えです!jQueryとバックボーンから来た私たちにとって、このような便利な例
Brandon

11
このように、このモジュールを使用して、同じビューで複数のディレクティブをインスタンス化することは可能ですか?このディレクティブの特定のインスタンスの成功またはエラー関数を呼び出すにはどうすればよいですか?
ira 2014年

3
@iraおそらく、ファクトリを変更して、ステータスオブジェクトとメッセージオブジェクトのマップ(またはリスト)を保持し、ディレクティブのname属性を使用して、リスト内の必要なアイテムを識別することができます。したがってsuccess(msg)、htmlを呼び出す代わりにsucess(name, msg)、正しい名前のディレクティブを選択するために呼び出します。
lanoxx 2014

5
@JoshDavidMillerなぜディレクティブでメソッドを呼び出すのは悪い習慣だと思いますか?ディレクティブが一部のDOMロジックを意図したとおりにカプセル化する場合、それを使用するコントローラーが必要に応じてメソッドを呼び出すことができるように、APIを公開するのは当然のことでしょう。
Paul Taylor、

27

イベントを使用してポップダウンをトリガーすることもできます。

satchmorunのソリューションに基づくフィドルがあります。これはPopdownAPIを省略し、代わりに最上位のコントローラー$broadcastがスコープチェーンの下の「成功」および「エラー」イベントを処理します。

$scope.success = function(msg) { $scope.$broadcast('success', msg); };
$scope.error   = function(msg) { $scope.$broadcast('error', msg); };

次に、Popdownモジュールはこれらのイベントのハンドラー関数を登録します。例:

$scope.$on('success', function(event, msg) {
    $scope.status = 'success';
    $scope.message = msg;
    $scope.toggleDisplay();
});

これは少なくとも機能し、私にはうまく分離されたソリューションのようです。これが何らかの理由で貧弱なプラクティスと見なされた場合は、他の人に呼びかけます。


1
私が考えることができる1つの欠点は、選択された回答ではPopdownAPI(DIで簡単に利用可能)のみが必要なことです。これでは、メッセージをブロードキャストするためにコントローラーのスコープにアクセスする必要があります。とにかく、とても簡潔に見えます。
ジュリアン

単純なユースケースでは、サービスアプローチよりもこれが好きです。複雑さが抑えられ、疎結合されているためです
Patrick Favre

11

またngFormname属性のように、ディレクティブのコントローラーを親スコープに公開することもできます:http : //docs.angularjs.org/api/ng.directive : ngForm

ここでは、非常に基本的な例を見つける方法を見つけることができますhttp://plnkr.co/edit/Ps8OXrfpnePFvvdFgYJf?p=preview

この例ではmyDirective$clearメソッド付きの専用コントローラー(ディレクティブ用の非常に単純なパブリックAPIの一種)を使用しています。このコントローラーを親スコープに公開し、ディレクティブの外でこのメソッドを呼び出すことができます。


これにはコントローラー間の関係が必要ですよね?OPはメッセージセンターを望んでいたので、これは彼にとって理想的ではないかもしれません。しかし、あなたのアプローチを学ぶこともとても良かったです。これは多くの状況で役立ち、あなたが言ったように、angular自体がそれを使用します。
fasfsfgs 2015

私はsatchmorunが提供する例に従うようにしています。実行時にHTMLを生成していますが、ディレクティブのテンプレートを使用していません。ディレクティブのコントローラーを使用して、追加されたhtmlから呼び出す関数を指定していますが、関数が呼び出されません。基本的に、私はこのディレクティブを持っています:directives.directive( 'abcXyz'、function($ compile {return {restrict: 'AE'、require: 'ngModel'、controller:function($ scope){$ scope.function1 = function() {..};}、私のhtmlは次のとおりです: "<a href="" ng-click="function1('itemtype')">
マーク

これは、ディレクティブがシングルトンでない場合にディレクティブAPIを公開できる唯一のエレガントなソリューションです!$scope.$parent[alias]内部の角度のあるAPIを使用するようなにおいがするので、私はまだ使用したくありません。しかし、それでもシングルトンではないディレクティブに対するよりエレガントなソリューションを見つけることはできません。イベントのブロードキャストや、ディレクティブapiの親コントローラーで空のオブジェクトを定義するような他のバリアントは、さらに臭いがします。
Ruslan Stelmachenko

3

私ははるかに良い解決策を得ました。

これが私のディレクティブです。ディレクティブ内のオブジェクト参照に注入し、ディレクティブコードに呼び出し機能を追加することで拡張しています。

app.directive('myDirective', function () {
    return {
        restrict: 'E',
        scope: {
        /*The object that passed from the cntroller*/
        objectToInject: '=',
        },
        templateUrl: 'templates/myTemplate.html',

        link: function ($scope, element, attrs) {
            /*This method will be called whet the 'objectToInject' value is changes*/
            $scope.$watch('objectToInject', function (value) {
                /*Checking if the given value is not undefined*/
                if(value){
                $scope.Obj = value;
                    /*Injecting the Method*/
                    $scope.Obj.invoke = function(){
                        //Do something
                    }
                }    
            });
        }
    };
});

パラメータを使用してHTMLでディレクティブを宣言します。

<my-directive object-to-inject="injectedObject"></ my-directive>

私のコントローラー:

app.controller("myController", ['$scope', function ($scope) {
   // object must be empty initialize,so it can be appended
    $scope.injectedObject = {};

    // now i can directly calling invoke function from here 
     $scope.injectedObject.invoke();
}];

これは基本的に、懸念の分離の原則に反します。ディレクティブにコントローラーでインスタンス化されたオブジェクトを提供し、そのオブジェクトの管理(つまり、invoke関数の作成)の責任をディレクティブに委任します。私の意見では、より良い解決策ではありません。
Florin Vistig 16
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.