AngularJSディレクティブで定義されたメソッドを呼び出す方法は?


297

私はディレクティブを持っています、これがコードです:

.directive('map', function() {
    return {
        restrict: 'E',
        replace: true,
        template: '<div></div>',
        link: function($scope, element, attrs) {

            var center = new google.maps.LatLng(50.1, 14.4); 
            $scope.map_options = {
                zoom: 14,
                center: center,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            };
            // create map
            var map = new google.maps.Map(document.getElementById(attrs.id), $scope.map_options);
            var dirService= new google.maps.DirectionsService();
            var dirRenderer= new google.maps.DirectionsRenderer()

            var showDirections = function(dirResult, dirStatus) {
                if (dirStatus != google.maps.DirectionsStatus.OK) {
                    alert('Directions failed: ' + dirStatus);
                    return;
                  }
                  // Show directions
                dirRenderer.setMap(map);
                //$scope.dirRenderer.setPanel(Demo.dirContainer);
                dirRenderer.setDirections(dirResult);
            };

            // Watch
            var updateMap = function(){
                dirService.route($scope.dirRequest, showDirections); 
            };    
            $scope.$watch('dirRequest.origin', updateMap);

            google.maps.event.addListener(map, 'zoom_changed', function() {
                $scope.map_options.zoom = map.getZoom();
              });

            dirService.route($scope.dirRequest, showDirections);  
        }
    }
})

updateMap()ユーザーアクションを呼び出します。アクションボタンがディレクティブにありません。

updateMap()コントローラーから呼び出す最良の方法は何ですか?


11
小さな注意事項:スコープはインジェクションされず、通常の引数として渡されるため、リンク関数では「スコープ」にドル記号を使用しないのが慣例です。
ノーム

回答:


369

分離スコープを使用=する場合は、コントローラースコープからの変数の双方向バインディングを使用して、コントロールオブジェクトを渡すことができます。また、同じコントロールオブジェクトを使用して、ページ上の同じディレクティブの複数のインスタンスを制御することもできます。

angular.module('directiveControlDemo', [])

.controller('MainCtrl', function($scope) {
  $scope.focusinControl = {};
})

.directive('focusin', function factory() {
  return {
    restrict: 'E',
    replace: true,
    template: '<div>A:{{internalControl}}</div>',
    scope: {
      control: '='
    },
    link: function(scope, element, attrs) {
      scope.internalControl = scope.control || {};
      scope.internalControl.takenTablets = 0;
      scope.internalControl.takeTablet = function() {
        scope.internalControl.takenTablets += 1;
      }
    }
  };
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="directiveControlDemo">
  <div ng-controller="MainCtrl">
    <button ng-click="focusinControl.takeTablet()">Call directive function</button>
    <p>
      <b>In controller scope:</b>
      {{focusinControl}}
    </p>
    <p>
      <b>In directive scope:</b>
      <focusin control="focusinControl"></focusin>
    </p>
    <p>
      <b>Without control object:</b>
      <focusin></focusin>
    </p>
  </div>
</div>


11
+1これは、Angularで再利用可能なコンポーネントのAPIを作成する方法でもあります。
romiem 2013

5
これは、受け入れられた回答よりもきれいで、私が間違っていない場合はシンプソンズのリファレンスに+1
Blake Miller

44
それがまさに私が同じ問題を解決した方法です。それは機能しますが、それはハックのように見えます...私はangularがこれに対してより良い解決策を持っていることを望みます。
Dema

1
私は角度を学んでいるので、私の意見はそれほど重要ではないかもしれませんが、私はこのアプローチが他の答えよりもはるかに直感的であり、それが正しい答えであるとマークしたでしょう。これをサンドボックスアプリケーションに問題なく実装しました。
BLSully 2014年

4
scope.control存在を確認するためにおそらくチェックを行う必要があります。そうでない場合、ディレクティブを使用するがディレクティブのメソッドにアクセスする必要がなく、controlattrを持たない他の場所では、属性を設定できないというエラーがスローされますundefined
CheapSteaks

73

アクションボタン$scopeがディレクティブと同じコントローラーを使用すると仮定すると、リンク関数内で関数updateMapを定義するだけ$scopeです。コントローラは、アクションボタンがクリックされたときにその関数を呼び出すことができます。

<div ng-controller="MyCtrl">
    <map></map>
    <button ng-click="updateMap()">call updateMap()</button>
</div>
app.directive('map', function() {
    return {
        restrict: 'E',
        replace: true,
        template: '<div></div>',
        link: function($scope, element, attrs) {
            $scope.updateMap = function() {
                alert('inside updateMap()');
            }
        }
    }
});

fiddle


@FlorianFのコメントによると、ディレクティブが分離されたスコープを使用する場合、事柄はより複雑になります。これを機能させる1つの方法は次のとおりです。ディレクティブ関数をコントローラーに登録するディレクティブにset-fn属性を追加しますmap

<map set-fn="setDirectiveFn(theDirFn)"></map>
<button ng-click="directiveFn()">call directive function</button>
scope: { setFn: '&' },
link: function(scope, element, attrs) {
    scope.updateMap = function() {
       alert('inside updateMap()');
    }
    scope.setFn({theDirFn: scope.updateMap});
}
function MyCtrl($scope) {
    $scope.setDirectiveFn = function(directiveFn) {
        $scope.directiveFn = directiveFn;
    };
}

fiddle


ディレクティブに独立したスコープがある場合はどうなりますか?
フロリアンF

ありがとう!(おそらく、ディレクティブのコントローラーで定義された関数を呼び出す方が簡単かもしれませんが、それについてはよくわかりません)
Florian F

1
分離されたスコープを扱っていない場合は、これがはるかに良い方法です。
Martin Frank

この答えは実際にはOPの質問に答えます。分離スコープも使用します。分離スコープを作成するには、scopeプロパティをディレクティブ宣言に追加するだけです。
ダニエルG.

35

ディレクティブの分離されたスコープでオブジェクトを公開して通信を促進するのは魅力的ですが、特に、この通信を2つのレベル(コントローラー、ディレクティブにチェーン)でつなぐ必要がある場合は、「スパゲッティ」コードが混乱する可能性があります。ネストされたディレクティブなど)

私たちは元々このパスをたどりましたが、さらに調査を行った結果、ディレクティブがサービス経由の通信に使用するイベントとプロパティを公開し、そのサービスのプロパティで$ watchを使用することで、より保守的で読みやすいコードが得られたことがわかりました通信のためにこれらの変更に対応する必要があるディレクティブまたはその他のコントロール。

この抽象化は、AngularJSの依存性注入フレームワークと非常にうまく機能し、これらのイベントに反応する必要のあるアイテムにサービスを注入できます。Angular.jsファイルを見ると、そこにあるディレクティブもこの方法でサービスと$ watchを使用していることがわかります。これらのディレクティブは、分離されたスコープでイベントを公開していません。

最後に、相互に依存するディレクティブ間で通信する必要がある場合は、通信手段としてこれらのディレクティブ間でコントローラーを共有することをお勧めします。

AngularJSのWiki for Best Practicesでもこれについて言及されています。

。$ broadcast()、。$ emit()、および。$ on()は、アトミックイベントにのみ使用してください。アプリ全体でグローバルに関連するイベント(ユーザー認証やアプリの終了など)。モジュール、サービス、またはウィジェットに固有のイベントが必要な場合は、サービス、ディレクティブコントローラー、またはサードパーティライブラリを検討する必要があります

  • $ scope。$ watch()はイベントの必要性を置き換える必要があります
  • サービスの注入とメソッドの直接呼び出しは、直接通信にも役立ちます
  • ディレクティブは、ディレクティブコントローラを介して互いに直接通信できます。

2
私は直感的に2つの解決策に到達しました。(1)スコープ変数の変更を監視し=ます。変数にはメソッド名と引数が含まれています。(2)一方向​​バインド文字列@をトピックIDとして公開し、呼び出し先にこのトピックのイベントを送信させます。今私はベストプラクティスのwikiを見ました。仕方がないのには理由があると思います。しかし、それがどのように機能するかはまだはっきりしていません。私の場合、タブセットディレクティブを作成しましたswitchTab(tabIndex)。メソッドを公​​開します。もっと例を挙げていただけますか?
stanleyxu2005 2015

switchTab(tabIndex)メソッドを公開するのではなく、tabIndex変数にバインドするだけです。ページコントローラーには、その変数を変更するアクションがある場合があります。その変数をタブディレクティブにバインド/渡します。タブディレクティブは、その変数の変更を監視し、独自のswitchTabを実行できます。ディレクティブが変数に基づいてタブを制御するタイミング/方法を決定するためです。それは外部ソースの仕事ではありません。そうでなければ、外部ソースはディレクティブの内部動作の知識を必要としますが、これは不正解です。
Suamere

15

Oliverの答えに基づいて構築する-ディレクティブの内部メソッドに常にアクセスする必要があるとは限らない場合があります。そのような場合、おそらくcontrolエラーをスローしないようにするために、空のオブジェクトを作成してattrをディレクティブに追加する必要はありません(cannot set property 'takeTablet' of undefined)。

ディレクティブ内の他の場所でメソッドを使用することもできます。

scope.control存在を確認するためのチェックを追加し、明らかにするモジュールパターンと同様の方法でそれにメソッドを設定します

app.directive('focusin', function factory() {
  return {
    restrict: 'E',
    replace: true,
    template: '<div>A:{{control}}</div>',
    scope: {
      control: '='
    },
    link : function (scope, element, attrs) {
      var takenTablets = 0;
      var takeTablet = function() {
        takenTablets += 1;  
      }

      if (scope.control) {
        scope.control = {
          takeTablet: takeTablet
        };
      }
    }
  };
});

ディレクティブ内で明確なパターンを使用すると、意図がはるかに明確になります。良いですね!
JSancho 2015

12

正直なところ、私はこのスレッドの答えのどれにも確信が持てませんでした。だから、これが私の解決策です:

ディレクティブハンドラー(マネージャー)のアプローチ

この方法は、ディレクティブ$scopeが共有されたものであるか孤立したものであるかを問わない

factoryディレクティブインスタンスを登録するためのA

angular.module('myModule').factory('MyDirectiveHandler', function() {
    var instance_map = {};
    var service = {
        registerDirective: registerDirective,
        getDirective: getDirective,
        deregisterDirective: deregisterDirective
    };

    return service;

    function registerDirective(name, ctrl) {
        instance_map[name] = ctrl;
    }

    function getDirective(name) {
        return instance_map[name];
    }

    function deregisterDirective(name) {
        instance_map[name] = null;
    }
});

ディレクティブコードは、通常、DOMを処理しないすべてのロジックをディレクティブコントローラー内に配置します。ハンドラー内にコントローラーインスタンスを登録します

angular.module('myModule').directive('myDirective', function(MyDirectiveHandler) {
    var directive = {
        link: link,
        controller: controller
    };

    return directive;

    function link() {
        //link fn code
    }

    function controller($scope, $attrs) {
        var name = $attrs.name;

        this.updateMap = function() {
            //some code
        };

        MyDirectiveHandler.registerDirective(name, this);

        $scope.$on('destroy', function() {
            MyDirectiveHandler.deregisterDirective(name);
        });
    }
})

テンプレートコード

<div my-directive name="foo"></div>

を使用してコントローラーインスタンスにアクセスしfactory、公開されているメソッドを実行します。

angular.module('myModule').controller('MyController', function(MyDirectiveHandler, $scope) {
    $scope.someFn = function() {
        MyDirectiveHandler.get('foo').updateMap();
    };
});

Angularのアプローチ

彼らがどのように対処するかについてのAngleの本から一枚を取り出す

<form name="my_form"></form>

$ parseを使用して、$parentスコープにコントローラーを登録します。この手法は、孤立した$scopeディレクティブでは機能しません。

angular.module('myModule').directive('myDirective', function($parse) {
    var directive = {
        link: link,
        controller: controller,
        scope: true
    };

    return directive;

    function link() {
        //link fn code
    }

    function controller($scope, $attrs) {
        $parse($attrs.name).assign($scope.$parent, this);

        this.updateMap = function() {
            //some code
        };
    }
})

を使用してコントローラ内にアクセスします $scope.foo

angular.module('myModule').controller('MyController', function($scope) {
    $scope.someFn = function() {
        $scope.foo.updateMap();
    };
});

「Angularのアプローチ」は見栄えがいいです!しかしタイプミスがあります:ある$scope.fooべきです$scope.my_form
ダニエルD

いや、それは$scope.foo私たちのテンプレート<div my-directive name="foo"></div>name属性の値が 'foo'だからです。<formこれは、この手法を採用したアングルの指令の1つの例に過ぎません
Mudassir Ali

10

少し遅れますが、これは分離されたスコープとディレクティブ内の関数を呼び出す「イベント」のソリューションです。このソリューションは、satchmorunによるこのSO投稿に触発され、モジュールとAPIを追加します。

//Create module
var MapModule = angular.module('MapModule', []);

//Load dependency dynamically
angular.module('app').requires.push('MapModule');

ディレクティブと通信するためのAPIを作成します。addUpdateEventはイベントをイベント配列に追加し、updateMapはすべてのイベント関数を呼び出します。

MapModule.factory('MapApi', function () {
    return {
        events: [],

        addUpdateEvent: function (func) {
            this.events.push(func);
        },

        updateMap: function () {
            this.events.forEach(function (func) {
                func.call();
            });
        }
    }
});

(イベントを削除する機能を追加する必要があるかもしれません。)

ディレクティブでMapAPIへの参照を設定し、MapApi.updateMapが呼び出されたときにイベントとして$ scope.updateMapを追加します。

app.directive('map', function () {
    return {
        restrict: 'E', 
        scope: {}, 
        templateUrl: '....',
        controller: function ($scope, $http, $attrs, MapApi) {

            $scope.api = MapApi;

            $scope.updateMap = function () {
                //Update the map 
            };

            //Add event
            $scope.api.addUpdateEvent($scope.updateMap);

        }
    }
});

「メイン」コントローラーでMapApiへの参照を追加し、MapApi.updateMap()を呼び出して地図を更新します。

app.controller('mainController', function ($scope, MapApi) {

    $scope.updateMapButtonClick = function() {
        MapApi.updateMap();    
    };
}

2
APIサービスに応じて同じタイプのディレクティブが複数ある場合、この提案は実際にはもう少し作業が必要になります。すべてを対象にするのではなく、1つの特定のディレクティブのみから関数をターゲットにして呼び出す必要がある状況になります。これに対する解決策で回答を強化しますか?
smajl 2016年

5

ディレクティブで親スコープの関数を定義できるようにするために使用できるDOM属性を指定できます。親スコープは、他のようにこのメソッドを呼び出すことができます。 ここに plunkerが。以下は関連するコードです。

clearfn ディレクティブ要素の属性で、親スコープはスコーププロパティを渡すことができます。スコーププロパティは、ディレクティブを目的の動作を実行する関数に設定できます。

<!DOCTYPE html>
<html ng-app="myapp">
  <head>
    <script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <style>
      my-box{
        display:block;
        border:solid 1px #aaa;
        min-width:50px;
        min-height:50px;
        padding:.5em;
        margin:1em;
        outline:0px;
        box-shadow:inset 0px 0px .4em #aaa;
      }
    </style>
  </head>
  <body ng-controller="mycontroller">
    <h1>Call method on directive</h1>
    <button ng-click="clear()">Clear</button>
    <my-box clearfn="clear" contentEditable=true></my-box>
    <script>
      var app = angular.module('myapp', []);
      app.controller('mycontroller', function($scope){
      });
      app.directive('myBox', function(){
        return {
          restrict: 'E',
          scope: {
            clearFn: '=clearfn'
          },
          template: '',
          link: function(scope, element, attrs){
            element.html('Hello World!');
            scope.clearFn = function(){
              element.html('');
            };
          }
        }
      });
    </script>
  </body>
</html>

なぜこれが機能するのか理解できません.. clear属性がスコープ内にあるためです。
Quinn Wilson、

1
宣言するとすぐに、ディレクティブのスコープの一部になります(例:)scope: { clearFn: '=clearfn' }
Trevor

2

scope。$ parentを使用して、呼び出された関数をディレクティブ関数に関連付けるだけです

angular.module('myApp', [])
.controller('MyCtrl',['$scope',function($scope) {

}])
.directive('mydirective',function(){
 function link(scope, el, attr){
   //use scope.$parent to associate the function called to directive function
   scope.$parent.myfunction = function directivefunction(parameter){
     //do something
}
}
return {
        link: link,
        restrict: 'E'   
      };
});

HTMLで

<div ng-controller="MyCtrl">
    <mydirective></mydirective>
    <button ng-click="myfunction(parameter)">call()</button>
</div>

2

メソッド名をディレクティブに指示して、コントローラーから呼び出すものを定義できますが、分離スコープはありません。

angular.module("app", [])
  .directive("palyer", [
    function() {
      return {
        restrict: "A",
        template:'<div class="player"><span ng-bind="text"></span></div>',
        link: function($scope, element, attr) {
          if (attr.toPlay) {
            $scope[attr.toPlay] = function(name) {
              $scope.text = name + " playing...";
            }
          }
        }
      };
    }
  ])
  .controller("playerController", ["$scope",
    function($scope) {
      $scope.clickPlay = function() {
        $scope.play('AR Song');
      };
    }
  ]);
.player{
  border:1px solid;
  padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
  <div ng-controller="playerController">
    <p>Click play button to play
      <p>
        <p palyer="" to-play="play"></p>
        <button ng-click="clickPlay()">Play</button>

  </div>
</div>


1

テスト済み これが誰かを助けることを願っています。

私の簡単なアプローチ(元のコードとしてタグを考える)

<html>
<div ng-click="myfuncion"> 
<my-dir callfunction="myfunction">
</html>

<directive "my-dir">
callfunction:"=callfunction"
link : function(scope,element,attr) {
scope.callfunction = function() {
 /// your code
}
}
</directive>

0

たぶん、これは最良の選択ではありませんが、ディレクティブのスコープやコントローラにアクセスしangular.element("#element").isolateScope()たり$("#element").isolateScope()、アクセスしたりできます。


0

ページコントローラでディレクティブのコントローラを取得する方法:

  1. カスタムディレクティブを記述して、DOM要素からディレクティブコントローラーへの参照を取得します。

    angular.module('myApp')
        .directive('controller', controller);
    
    controller.$inject = ['$parse'];
    
    function controller($parse) {
        var directive = {
            restrict: 'A',
            link: linkFunction
        };
        return directive;
    
        function linkFunction(scope, el, attrs) {
            var directiveName = attrs.$normalize(el.prop("tagName").toLowerCase());
            var directiveController = el.controller(directiveName);
    
            var model = $parse(attrs.controller);
            model.assign(scope, directiveController);
        }
    }
  2. ページコントローラのhtmlで使用します。

    <my-directive controller="vm.myDirectiveController"></my-directive>
  3. ページコントローラでディレクティブコントローラを使用します。

    vm.myDirectiveController.callSomeMethod();

注:指定されたソリューションは、要素ディレクティブのコントローラーに対してのみ機能します(必要なディレクティブの名前を取得するためにタグ名が使用されます)。


0

以下の解決策は、コントローラー(親とディレクティブ(分離された)の両方)が 'controller As'形式である場合に役立ちます

誰かがこれを役に立つと思うかもしれません

ディレクティブ:

var directive = {
        link: link,
        restrict: 'E',
        replace: true,
        scope: {
            clearFilters: '='
        },
        templateUrl: "/temp.html",
        bindToController: true, 
        controller: ProjectCustomAttributesController,
        controllerAs: 'vmd'
    };
    return directive;

    function link(scope, element, attrs) {
        scope.vmd.clearFilters = scope.vmd.SetFitlersToDefaultValue;
    }
}

ディレクティブController:

function DirectiveController($location, dbConnection, uiUtility) {
  vmd.SetFitlersToDefaultValue = SetFitlersToDefaultValue;

function SetFitlersToDefaultValue() {
           //your logic
        }
}

HTMLコード:

      <Test-directive clear-filters="vm.ClearFilters"></Test-directive>
    <a class="pull-right" style="cursor: pointer" ng-click="vm.ClearFilters()"><u>Clear</u></a> 
//this button is from parent controller which will call directive controller function
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.