あるAngularJSコントローラーが別のコントローラーを呼び出すことはできますか?


581

1つのコントローラーで別のコントローラーを使用することはできますか?

例えば:

このHTMLドキュメントは、MessageCtrlコントローラによって配信されたメッセージをmessageCtrl.jsファイルに出力するだけです。

<html xmlns:ng="http://angularjs.org/">
<head>
    <meta charset="utf-8" />
    <title>Inter Controller Communication</title>
</head>
<body>
    <div ng:controller="MessageCtrl">
        <p>{{message}}</p>
    </div>

    <!-- Angular Scripts -->
    <script src="http://code.angularjs.org/angular-0.9.19.js" ng:autobind></script>
    <script src="js/messageCtrl.js" type="text/javascript"></script>
</body>
</html>

コントローラファイルには、次のコードが含まれています。

function MessageCtrl()
{
    this.message = function() { 
        return "The current date is: " + new Date().toString(); 
    };
}

現在の日付を出力するだけです。

DateCtrl特定の形式で日付をに戻す別のコントローラーを追加する場合MessageCtrl、どうすればこれを実行できますか?DIフレームワークはXmlHttpRequestsサービスに関係し、サービスにアクセスしているようです。


4
このgoogleグループスレッドgroups.google.com/d/topic/angular/m_mn-8gnNt4/discussionは、コントローラが互いに通信する5つの方法について説明します。
Mark Rajcok

ここにはすでに良い答えがあるので、言及した特定のユースケースでは、おそらくAngularJSフィルターの方が優れたソリューションになるでしょう。私はそれを言及するだろうと思っただけです:)
ジョーDyndale

回答:


705

コントローラ間で通信する方法は複数あります。

最良のものはおそらくサービスを共有することです:

function FirstController(someDataService) 
{
  // use the data service, bind to template...
  // or call methods on someDataService to send a request to server
}

function SecondController(someDataService) 
{
  // has a reference to the same instance of the service
  // so if the service updates state for example, this controller knows about it
}

別の方法は、スコープでイベントを発行することです。

function FirstController($scope) 
{
  $scope.$on('someEvent', function(event, args) {});
  // another controller or even directive
}

function SecondController($scope) 
{
  $scope.$emit('someEvent', args);
}

どちらの場合も、任意のディレクティブと通信できます。


4
Hia、最初の例では、Webページがスタック内のすべてのサービスを認識する必要があります。嫌なにおいがする?2番目と同様に、Webページは$ scope引数を提供する必要はありませんか?
BanksySan

54
何?どうして?すべてのコントローラーはAngularのDIによって注入されます。
Vojta

7
@JoshNoe in 1/2つのコントローラー(またはそれ以上)があり、どちらも1つの同一/共有サービスを取得します。次に、あなたはコミュニケーションの方法をいくつか持っています。特定のユースケースに基づいて決定します。共有ロジック/状態をサービスに入れることができ、両方のコントローラーはそのサービスに委任するか、サービスをテンプレートにエクスポートすることさえできます。もちろん、サービスはイベントを発生させることもできます...
ヴォイタ2013

137
この遅くに来る:あなたたちは、あなたがAngularJSに取り組んでいるGoogleのTHE Vojtaと議論していることを知っていますよね?:)
スーマン

16
私のHTMLでは、イベント発行コントローラーが機能するためには、リスニングコントローラーの子ノードである必要があることは明らかではありませんでした。
djangonaut 2014年

122

このフィドルを参照してください: http //jsfiddle.net/simpulton/XqDxG/

次のビデオもご覧ください。 ご覧ください。コントローラー間の通信

HTML:

<div ng-controller="ControllerZero">
  <input ng-model="message" >
  <button ng-click="handleClick(message);">LOG</button>
</div>

<div ng-controller="ControllerOne">
  <input ng-model="message" >
</div>

<div ng-controller="ControllerTwo">
  <input ng-model="message" >
</div>

javascript:

var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
  var sharedService = {};

  sharedService.message = '';

  sharedService.prepForBroadcast = function(msg) {
    this.message = msg;
    this.broadcastItem();
  };

  sharedService.broadcastItem = function() {
    $rootScope.$broadcast('handleBroadcast');
  };

  return sharedService;
});

function ControllerZero($scope, sharedService) {
  $scope.handleClick = function(msg) {
    sharedService.prepForBroadcast(msg);
  };

  $scope.$on('handleBroadcast', function() {
    $scope.message = sharedService.message;
  });        
}

function ControllerOne($scope, sharedService) {
  $scope.$on('handleBroadcast', function() {
    $scope.message = 'ONE: ' + sharedService.message;
  });        
}

function ControllerTwo($scope, sharedService) {
  $scope.$on('handleBroadcast', function() {
    $scope.message = 'TWO: ' + sharedService.message;
  });
}

ControllerZero.$inject = ['$scope', 'mySharedService'];        

ControllerOne.$inject = ['$scope', 'mySharedService'];

ControllerTwo.$inject = ['$scope', 'mySharedService'];

12
上記のフィドルとビデオはサービスを共有します。$ scope。$ emitを使用するフィドルは次のとおり
Mark Rajcok

1
@adardesign:ディレクティブの同じ簡潔で意味のある例を読んでみたいと思います(この回答にも感謝します!)
sscarduzio 2014

すばらしい回答です。myModule.factoryの代わりにmyModule.service( 'mySharedService'、function($ rootScope){})を使用していますが、それでも機能します!
TacoEater 2015

優秀な。しかし、私は質問があります:なぜControllerZero内にハンドラーを追加したのですか?$ scope。$ on( 'handleBroadcast'、function(){$ scope.message = sharedService.message;});
ZooZ

提供されたビデオは本当に素晴らしいです!これは別のコントローラから別のコントローラの状態を問い合わせるのに必要なことのようです。ただし、これは「呼び出し」機能を使用して機能しません。「トリガー」アクションを使用して動作します。つまり、コントローラーがアクションを実行して新しい状態になった場合、その状態をブロードキャストする必要があり、他のコントローラーがそのブロードキャストをリッスンしてそれに応じて応答する必要があります。または、共有サービスでアクションを実行してから、状態をブロードキャストします。私の理解が正しいか教えてください。
tarekahf 2016

53

あるコントローラーを別のコントローラーに呼び出したい場合は、4つの方法を使用できます。

  1. $ rootScope。$ emit()および$ rootScope。$ broadcast()
  2. 2番目のコントローラーが子の場合、親子通信を使用できます。
  3. 利用サービス
  4. ハックの種類-angular.element()の助けを借りて

1. $ rootScope。$ emit()および$ rootScope。$ broadcast()

コントローラとそのスコープは破棄される可能性がありますが、$ rootScopeはアプリケーション全体に残ります。これが、$ rootScopeがすべてのスコープの親であるため、$ rootScopeを使用している理由です。

親から子への通信を行っており、子供でさえ兄弟と通信したい場合は、$ broadcastを使用できます。

子から親への通信を実行していて、兄弟が呼び出されていない場合は、$ rootScope。$ emitを使用できます。

HTML

<body ng-app="myApp">
    <div ng-controller="ParentCtrl" class="ng-scope">
      // ParentCtrl
      <div ng-controller="Sibling1" class="ng-scope">
        // Sibling first controller
      </div>
      <div ng-controller="Sibling2" class="ng-scope">
        // Sibling Second controller
        <div ng-controller="Child" class="ng-scope">
          // Child controller
        </div>
      </div>
    </div>
</body>

Angularjsコード

 var app =  angular.module('myApp',[]);//We will use it throughout the example 
    app.controller('Child', function($rootScope) {
      $rootScope.$emit('childEmit', 'Child calling parent');
      $rootScope.$broadcast('siblingAndParent');
    });

app.controller('Sibling1', function($rootScope) {
  $rootScope.$on('childEmit', function(event, data) {
    console.log(data + ' Inside Sibling one');
  });
  $rootScope.$on('siblingAndParent', function(event, data) {
    console.log('broadcast from child in parent');
  });
});

app.controller('Sibling2', function($rootScope) {
  $rootScope.$on('childEmit', function(event, data) {
    console.log(data + ' Inside Sibling two');
  });
  $rootScope.$on('siblingAndParent', function(event, data) {
    console.log('broadcast from child in parent');
  });
});

app.controller('ParentCtrl', function($rootScope) {
  $rootScope.$on('childEmit', function(event, data) {
    console.log(data + ' Inside parent controller');
  });
  $rootScope.$on('siblingAndParent', function(event, data) {
    console.log('broadcast from child in parent');
  });
});

上記のコードコンソールでは、$ emitの 'childEmit'は子の兄弟の内部を呼び出さず、親の内部のみを呼び出します。$ broadcastは兄弟と親の内部でも呼び出されます。これは、パフォーマンスが動作する場所です。$ emitはいくつかのダーティーチェックをスキップするため、親から子への通信を使用している場合に推奨されます。

2. 2番目のコントローラーが子の場合、子と親の通信を使用できます

その最良の方法の1つです。子が直接の親と通信したい場合に子と親の通信を行いたい場合は、$ broadcastや$ emitは必要ありませんが、親から子への通信を行いたい場合は、 serviceまたは$ broadcastを使用する

たとえばHTML:-

<div ng-controller="ParentCtrl">
 <div ng-controller="ChildCtrl">
 </div>
</div>

Angularjs

 app.controller('ParentCtrl', function($scope) {
   $scope.value='Its parent';
      });
  app.controller('ChildCtrl', function($scope) {
   console.log($scope.value);
  });

子から親への通信を使用しているときは常に、Angularjsは子の内部の変数を検索します。変数が内部に存在しない場合は、親コントローラー内の値を表示することを選択します。

3.利用サービス

AngularJSは、サービスアーキテクチャを使用して「懸念の分離」の概念をサポートしています。サービスはJavaScript関数であり、特定のタスクのみを実行する責任があります。これにより、サービスは保守およびテストが可能な個別のエンティティになります。Angularjsの依存性注入メカニズムを使用して注入するために使用されるサービス。

Angularjsコード:

app.service('communicate',function(){
  this.communicateValue='Hello';
});

app.controller('ParentCtrl',function(communicate){//Dependency Injection
  console.log(communicate.communicateValue+" Parent World");
});

app.controller('ChildCtrl',function(communicate){//Dependency Injection
  console.log(communicate.communicateValue+" Child World");
});

Hello Child WorldとHello Parent Worldが出力されます。Angularのサービスドキュメントシングルトンによれば、サービスに依存する各コンポーネントは、サービスファクトリによって生成された単一のインスタンスへの参照を取得します

4.ハックの種類-angular.element()の助けを借りて

このメソッドは、Id /一意のclass.angular.element()メソッドによって要素からscope()を取得し、要素を返します。scope()は、別の内部の1つのコントローラーの$ scope変数を使用して別の変数の$ scope変数を提供することはお勧めできません。

HTML:-

<div id='parent' ng-controller='ParentCtrl'>{{varParent}}
 <span ng-click='getValueFromChild()'>Click to get ValueFormChild</span>
 <div id='child' ng-controller='childCtrl'>{{varChild}}
   <span ng-click='getValueFromParent()'>Click to get ValueFormParent </span>
 </div>
</div>

Angularjs:-

app.controller('ParentCtrl',function($scope){
 $scope.varParent="Hello Parent";
  $scope.getValueFromChild=function(){
  var childScope=angular.element('#child').scope();
  console.log(childScope.varChild);
  }
});

app.controller('ChildCtrl',function($scope){
 $scope.varChild="Hello Child";
  $scope.getValueFromParent=function(){
  var parentScope=angular.element('#parent').scope();
  console.log(parentScope.varParent);
  }
}); 

上記のコードでは、コントローラーはHTMLで独自の値を表示しており、テキストをクリックすると、それに応じてコンソールに値が表示されます。親コントローラースパンをクリックすると、ブラウザーは子の値をコンソールし、逆も同様です。


52

以下は、サービスデータを共有する2つのコントローラーの1ページの例です。

<!doctype html>
<html ng-app="project">
<head>
    <title>Angular: Service example</title>
    <script src="http://code.angularjs.org/angular-1.0.1.js"></script>
    <script>
var projectModule = angular.module('project',[]);

projectModule.factory('theService', function() {  
    return {
        thing : {
            x : 100
        }
    };
});

function FirstCtrl($scope, theService) {
    $scope.thing = theService.thing;
    $scope.name = "First Controller";
}

function SecondCtrl($scope, theService) {   
    $scope.someThing = theService.thing; 
    $scope.name = "Second Controller!";
}
    </script>
</head>
<body>  
    <div ng-controller="FirstCtrl">
        <h2>{{name}}</h2>
        <input ng-model="thing.x"/>         
    </div>

    <div ng-controller="SecondCtrl">
        <h2>{{name}}</h2>
        <input ng-model="someThing.x"/>             
    </div>
</body>
</html>

こちらも:https : //gist.github.com/3595424


場合やtheService、更新thing.x、その変更は自動的に<入力>でSにpropageates FirstCtrlSecondCtrl、右?またthing.x、2つの<input>のいずれかを介して直接変更することもできます(右?)。
KajMagnus

4
はい。すべてのAngularサービスはアプリケーションシングルトンです。つまり、theServiceのインスタンスは1つだけです。リファレンス:docs.angularjs.org/guide/dev_guide.services.creating_services
exclsr

私の以前のコメントのリンクは404なので、ここにサービスガイドがあります。今日は、サービスがシングルトンであることに注意してください:docs.angularjs.org/guide/services
exclsr

1
@exclsrはい!申し訳ありませんが、以前に見逃しました
CodyBugstein 14年

3
これまでにWebで見た中で最も優れた例です。ありがとう
セブンアース2015

33

イベントを発信およびブロードキャストして、コントローラー間でデータを共有したり、関数を呼び出したりする場合は、このリンクを参照し、回答を確認してくださいzbynour(最大投票数で回答)。私は彼の答えを引用しています!!!

firstCtrlのスコープがsecondCtrlスコープの親である場合、コードはfirstCtrlで$ emitを$ broadcastに置き換えることで機能します。

function firstCtrl($scope){
    $scope.$broadcast('someEvent', [1,2,3]);
}

function secondCtrl($scope){
    $scope.$on('someEvent', function(event, mass) {console.log(mass)});
}

スコープ間に親子関係がない場合は、$ rootScopeをコントローラーに注入し、すべての子スコープ(つまり、secondCtrl)にイベントをブロードキャストできます。

function firstCtrl($rootScope){
    $rootScope.$broadcast('someEvent', [1,2,3]);
}

最後に、イベントを子コントローラーからスコープの上方向にディスパッチする必要がある場合は、$ scope。$ emitを使用できます。firstCtrlのスコープがsecondCtrlスコープの親である場合:

function firstCtrl($scope){
    $scope.$on('someEvent', function(event, data) { console.log(data); });
}

function secondCtrl($scope){
    $scope.$emit('someEvent', [1,2,3]);
}

24

さらに2つのフィドル:(非サービスアプローチ)

1)$scope親子コントローラーの場合- イベントを発行/ブロードキャストするための親コントローラーの使用。 http://jsfiddle.net/laan_sachin/jnj6y/

2)$rootScope関連しないコントローラ間での使用。 http://jsfiddle.net/VxafF/


イベントのこの複雑さの理由は何ですか?このようなことをしてみませんか?jsfiddle.net/jnj6y/32
Dfr

それはどのような親子関係の権利に依存します。それはDOM階層であるかもしれません、それはそのケースイベントが事物を分離することを可能にするでしょう。
DarkKnight 2013年

17

イベントはスコープ階層を上下にバブルし、複雑なアプリケーションのパフォーマンスのボトルネックになりやすいため、実際に発行およびブロードキャストを使用することは非効率的です。

サービスの利用をお勧めします。これが私が最近プロジェクトの1つに実装した方法です-https://gist.github.com/3384419

基本的な考え方-pub-sub / eventバスをサービスとして登録します。次に、イベント/トピックをサブスクライブまたはパブリッシュする必要がある場所にそのイベントバスを挿入します。


5

私もこの方法を知っています。

angular.element($('#__userProfile')).scope().close();

しかし、角度コードでjQueryセレクターを使用したくないので、あまり使用しません。


最良の答え。とてもシンプルで簡単... ​​=)
zVictor 2014

3
@zVictor、これは本当に「最後の手段」タイプのアプローチです。機能しますが、強制的に戻すためにスコープから外れています。これは、DOM操作を使用して、プログラムで行うのではなく、何かを強制的に実行しています。シンプルで機能しますが、拡張性はありません。
ブライアンノア

2
@BrianNoah、本当です。このコードをプロトタイプやいくつかの実験に使用しても問題ありませんが、量産コードには使用できません。
Andrey Korchak 2014

1
それができる最悪の事態です。サービスでのDOM操作とスコープへの直接アクセス。
Mattia Franchetto 2015年

3

そこのサービスに依存しない方法がある、$broadcastまたは$emit。すべての場合に適しているわけではありませんが、ディレクティブに抽象化できる2つの関連するコントローラーがあるrequire場合は、ディレクティブ定義でオプションを使用できます。これは、ngModelとngFormが通信する方法と考えられます。これを使用して、ネストされている、または同じ要素上にあるディレクティブコントローラ間で通信できます。

親/子の状況では、次のように使用します。

<div parent-directive>
  <div inner-directive></div>
</div>

そして、それを機能させるための主なポイント:親ディレクティブで、呼び出されるメソッドを使用して、this(ではなく)それらを定義する必要があります$scope)で。

controller: function($scope) {
  this.publicMethodOnParentDirective = function() {
    // Do something
  }
}

子ディレクティブの定義では、requireオプションを使用して、親コントローラーがリンク関数に渡されるようにすることができます(これにより、リンクから関数を呼び出すことができます)scope、子ディレクティブの。

require: '^parentDirective',
template: '<span ng-click="onClick()">Click on this to call parent directive</span>',
link: function link(scope, iElement, iAttrs, parentController) {
  scope.onClick = function() {
    parentController.publicMethodOnParentDirective();
  }
}

上記は、 http://plnkr.co/edit/poeq460VmQER8Gl9w8Oz?p=preview

兄弟ディレクティブも同様に使用されますが、両方のディレクティブが同じ要素にあります。

<div directive1 directive2>
</div>

でメソッドを作成して使用しdirective1ます:

controller: function($scope) {
  this.publicMethod = function() {
    // Do something
  }
}

そしてdirective2では、これはrequireオプションを使用して呼び出すことができ、その結果、siblingControllerがリンク関数に渡されます:

require: 'directive1',
template: '<span ng-click="onClick()">Click on this to call sibling directive1</span>',
link: function link(scope, iElement, iAttrs, siblingController) {
  scope.onClick = function() {
    siblingController.publicMethod();
  }
}

これはで見ることができます http://plnkr.co/edit/MUD2snf9zvadfnDXq85w?p=previewで

これの用途は?

  • 親:子要素が自身を親に「登録」する必要がある場合。ngModelとngFormの関係によく似ています。これらは、モデルに影響を与える可能性がある特定の動作を追加できます。純粋にDOMベースのものもあるかもしれません。親要素は特定の子の位置を管理する必要があります。たとえば、スクロールを管理したり、スクロールに反応したりする必要があります。

  • 兄弟:ディレクティブの動作を変更できるようにします。ngModelは古典的なケースで、入力でのngModelの使用にパーサー/検証を追加します。


3

これが標準外であるかどうかはわかりませんが、すべてのコントローラーが同じファイルにある場合は、次のようにすることができます。

app = angular.module('dashboardBuzzAdmin', ['ngResource', 'ui.bootstrap']);

var indicatorsCtrl;
var perdiosCtrl;
var finesCtrl;

app.controller('IndicatorsCtrl', ['$scope', '$http', function ($scope, $http) {
  indicatorsCtrl = this;
  this.updateCharts = function () {
    finesCtrl.updateChart();
    periodsCtrl.updateChart();
  };
}]);

app.controller('periodsCtrl', ['$scope', '$http', function ($scope, $http) {
  periodsCtrl = this;
  this.updateChart = function() {...}
}]);

app.controller('FinesCtrl', ['$scope', '$http', function ($scope, $http) {
  finesCtrl = this;
  this.updateChart = function() {...}
}]);

ご覧のとおり、indicatorsCtrlはupdateChartsを呼び出すときに他の両方のコントローラーのupdateChart関数を呼び出しています。


2

親コントローラー(MessageCtrl)に「$ controller」サービスを挿入し、次のコマンドを使用して子コントローラー(DateCtrl)をインスタンス化/挿入できます。
$scope.childController = $controller('childController', { $scope: $scope.$new() });

これで、サービスであるメソッドを呼び出すことにより、子コントローラーからデータにアクセスできます。
問題があれば教えてください。


1

以下は、publish-subscribeAngular JSに関係のないアプローチです。

パラメータコントローラを検索

//Note: Multiple entities publish the same event
regionButtonClicked: function () 
{
        EM.fireEvent('onSearchParamSelectedEvent', 'region');
},

plantButtonClicked: function () 
{
        EM.fireEvent('onSearchParamSelectedEvent', 'plant');
},

検索選択肢コントローラー

//Note: It subscribes for the 'onSearchParamSelectedEvent' published by the Search Param Controller
localSubscribe: function () {
        EM.on('onSearchParamSelectedEvent', this.loadChoicesView, this);

});


loadChoicesView: function (e) {

        //Get the entity name from eData attribute which was set in the event manager
        var entity = $(e.target).attr('eData');

        console.log(entity);

        currentSelectedEntity = entity;
        if (entity == 'region') {
            $('.getvalue').hide();
            this.loadRegionsView();
            this.collapseEntities();
        }
        else if (entity == 'plant') {
            $('.getvalue').hide();
            this.loadPlantsView();
            this.collapseEntities();
        }


});

イベントマネージャ

myBase.EventManager = {

    eventArray:new Array(),


    on: function(event, handler, exchangeId) {
        var idArray;
        if (this.eventArray[event] == null) {
            idArray = new Array();
        } else { 
            idArray = this.eventArray[event];
        }
        idArray.push(exchangeId);
        this.eventArray[event] = idArray;

        //Binding using jQuery
        $(exchangeId).bind(event, handler);
    },

    un: function(event, handler, exchangeId) {

        if (this.eventArray[event] != null) {
            var idArray = this.eventArray[event];
            idArray.pop(exchangeId);
            this.eventArray[event] = idArray;

            $(exchangeId).unbind(event, handler);
        }
    },

    fireEvent: function(event, info) {
        var ids = this.eventArray[event];

        for (idindex = 0; idindex < ids.length; idindex++) {
            if (ids[idindex]) {

                //Add attribute eData
                $(ids[idindex]).attr('eData', info);
                $(ids[idindex]).trigger(event);
            }
        }
    }
};

グローバル

var EM = myBase.EventManager;

1

角度1.5では、これは以下を実行することによって達成できます:

(function() {
  'use strict';

  angular
    .module('app')
    .component('parentComponent',{
      bindings: {},
      templateUrl: '/templates/products/product.html',
      controller: 'ProductCtrl as vm'
    });

  angular
    .module('app')
    .controller('ProductCtrl', ProductCtrl);

  function ProductCtrl() {
    var vm = this;
    vm.openAccordion = false;

    // Capture stuff from each of the product forms
    vm.productForms = [{}];

    vm.addNewForm = function() {
      vm.productForms.push({});
    }
  }

}());

これは親コンポーネントです。これで、別のオブジェクトをproductForms配列にプッシュする関数を作成しました-注-これは単なる例であり、この関数は実際には何でもかまいません。

これで、次のものを利用する別のコンポーネントを作成できますrequire

(function() {
  'use strict';

  angular
    .module('app')
    .component('childComponent', {
      bindings: {},
      require: {
        parent: '^parentComponent'
      },
      templateUrl: '/templates/products/product-form.html',
      controller: 'ProductFormCtrl as vm'
    });

  angular
    .module('app')
    .controller('ProductFormCtrl', ProductFormCtrl);

  function ProductFormCtrl() {
    var vm = this;

    // Initialization - make use of the parent controllers function
    vm.$onInit = function() {
      vm.addNewForm = vm.parent.addNewForm;
    };  
  }

}());

ここで、子コンポーネントは親コンポーネント関数への参照を作成しています。addNewFormこれは、HTMLにバインドして、他の関数と同様に呼び出すことができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.