ChatGPT解决这个技术问题 Extra ChatGPT

Can one AngularJS controller call another?

Is it possible to have one controller use another?

For example:

This HTML document simply prints a message delivered by the MessageCtrl controller in the messageCtrl.js file.

<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>

The controller file contains the following code:

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

Which simply prints the current date;

If I were to add another controller, DateCtrl which handed the date in a specific format back to MessageCtrl, how would one go about doing this? The DI framework seems to be concerned with XmlHttpRequests and accessing services.

This google group thread, groups.google.com/d/topic/angular/m_mn-8gnNt4/discussion, discusses 5 ways controllers can talk to each other.
There are good answers here already, so I'd just like to point out that for the particular use case mentioned, perhaps an AngularJS filter would be a better solution? Just thought I'd mention it :)

G
George G

There are multiple ways how to communicate between controllers.

The best one is probably sharing a service:

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
}

Another way is emitting an event on scope:

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

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

In both cases, you can communicate with any directive as well.


Hia, The first example would require the web page to be aware of all the services in the stack. Which feels like a bad smell (?). As with the second, wouldn't the web page need to provide the $scope argument?
What? Why? All controllers are injected by Angular's DI.
@JoshNoe in 1/ you have two controllers (or more) and they both get one identical/shared service. Then, you have multiple ways how to communicate, some of them you mentioned. I would decide based on your specific use case. You can put the shared logic/state into the service and both controllers only delegate to that service or even export the service to the template. Of course, the service can also fire events...
Coming to this late: you guys do know you are arguing with THE Vojta from Google who works on AngularJS, right? :)
It wasn't obvious to me that in my HTML the event-emitting controller has to be a child-node of the listening controller for it to work.
J
Josh Noe

See this fiddle: http://jsfiddle.net/simpulton/XqDxG/

Also watch the following video: Communicating Between Controllers

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'];

The above fiddle and video share a service. Here is a fiddle that uses $scope.$emit: jsfiddle.net/VxafF
@adardesign: I'd LOVE to read the same succinct and meaningful example for directives (thanks for this answer too!)
Great Answer, I use myModule.service('mySharedService', function($rootScope) {}) instead of myModule.factory but it works none the less!
Excellent. Though, I have a question: Why did you add a handler within ControllerZero? $scope.$on('handleBroadcast', function() { $scope.message = sharedService.message; });
The video provided is really awesome ! I it seems this is what I need to enquire the state of another controller from another controller. However, this does not work using "invoke" function. It works using "trigger" action. So effectively, if a controller carries out an action, and has a new state, then, it will have to broadcast the state, and it is up to other controllers to listen to that broadcast and respond accordingly. Or better, perform the action in the shared service, then broadcast the state. Please tell me if my understanding is correct.
f
falvojr

If you want to call one controller into another there are four methods available

$rootScope.$emit() and $rootScope.$broadcast() If Second controller is child ,you can use Parent child communication . Use Services Kind of hack - with the help of angular.element()

1. $rootScope.$emit() and $rootScope.$broadcast()

Controller and its scope can get destroyed, but the $rootScope remains across the application, that's why we are taking $rootScope because $rootScope is parent of all scopes .

If you are performing communication from parent to child and even child wants to communicate with its siblings, you can use $broadcast

If you are performing communication from child to parent ,no siblings invovled then you can use $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 Code

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

In above code console of $emit 'childEmit' will not call inside child siblings and It will call inside only parent, where $broadcast get called inside siblings and parent as well.This is the place where performance come into a action.$emit is preferrable, if you are using child to parent communication because it skips some dirty checks.

2. If Second controller is child, you can use Child Parent communication

Its one of the best method, If you want to do child parent communication where child wants to communicate with immediate parent then it would not need any kind $broadcast or $emit but if you want to do communication from parent to child then you have to use either service or $broadcast

For example 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);
  });

Whenever you are using child to parent communication, Angularjs will search for a variable inside child, If it is not present inside then it will choose to see the values inside parent controller.

3.Use Services

AngularJS supports the concepts of "Seperation of Concerns" using services architecture. Services are javascript functions and are responsible to do a specific tasks only.This makes them an individual entity which is maintainable and testable.Services used to inject using Dependency Injection mecahnism of Angularjs.

Angularjs code:

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

It will give output Hello Child World and Hello Parent World . According to Angular docs of services Singletons – Each component dependent on a service gets a reference to the single instance generated by the service factory.

4.Kind of hack - with the help of angular.element()

This method gets scope() from the element by its Id / unique class.angular.element() method returns element and scope() gives $scope variable of another variable using $scope variable of one controller inside another is not a good practice.

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

In above code controllers are showing their own value on Html and when you will click on text you will get values in console accordingly.If you click on parent controllers span, browser will console value of child and viceversa.


e
exclsr

Here is a one-page example of two controllers sharing service data:

<!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>

Also here: https://gist.github.com/3595424


And if theService updates thing.x, then that change automatically propageates to the <input>s in FirstCtrl and SecondCtrl, right? And one can also change thing.x directly via any of the two <input>s (right?).
Yes. All Angular services are application singletons, which means there is only one instance of theService. Reference: docs.angularjs.org/guide/dev_guide.services.creating_services
The link in my previous comment is 404, so here is the services guide, today, that notes services are singletons: docs.angularjs.org/guide/services
@exclsr Yes! Sorry I missed that before
By far the best example I've seen on the web so far. Thank you
C
Community

If you are looking to emit & broadcast events to share data or call functions across controllers, please look at this link: and check the answer by zbynour (answer with max votes). I am quoting his answer !!!

If scope of firstCtrl is parent of the secondCtrl scope, your code should work by replacing $emit by $broadcast in firstCtrl:

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

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

In case there is no parent-child relation between your scopes you can inject $rootScope into the controller and broadcast the event to all child scopes (i.e. also secondCtrl).

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

Finally, when you need to dispatch the event from child controller to scopes upwards you can use $scope.$emit. If scope of firstCtrl is parent of the secondCtrl scope:

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

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

E
Erwin

Two more fiddles: (Non service approach)

1) For Parent- Child controller - Using $scope of parent controller to emit/broadcast events. http://jsfiddle.net/laan_sachin/jnj6y/

2) Using $rootScope across non-related controllers. http://jsfiddle.net/VxafF/


What reason for all this complexity with events ? Why not do something like this ? jsfiddle.net/jnj6y/32
It depends on what kind of Parent Child relationship right. It might be a DOM heirarchy, it that case events would allow you to decouple things.
R
Rahi.Shah

Actually using emit and broadcast is inefficient because the event bubbles up and down the scope hierarchy which can easily degrade into performance bottlement for a complex application.

I would suggest using a service. Here is how I recently implemented it in one of my projects - https://gist.github.com/3384419.

Basic idea - register a pub-sub/event bus as a service. Then inject that event bus where ever you need to subscribe or publish events/topics.


B
BanksySan

I also know of this way.

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

But I don't use it too much, because I don't like to use jQuery selectors in angular code.


the best answer. So simple and easy... = )
@zVictor, this is really a "last resort" type of approach. It works, but it's breaking out of the scope in order to force your way back in. This is using DOM manipulation to force something to be done instead of programmatically doing it. It's simple, it works, but it's not scaleable.
@BrianNoah, true. It's ok use this code for prototypes or some experiments, but not for production code.
That's the worst that can be done. DOM manipulation in services and direct scope access.
M
Michal Charemza

There is a method not dependent on services, $broadcast or $emit. It's not suitable in all cases, but if you have 2 related controllers that can be abstracted into directives, then you can use the require option in the directive definition. This is most likely how ngModel and ngForm communicate. You can use this to communicate between directive controllers that are either nested, or on the same element.

For a parent/child situation, the use would be as follows:

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

And the main points to get it working: On the parent directive, with the methods to be called, you should define them on this (not on $scope):

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

On the child directive definition, you can use the require option so the parent controller is passed to the link function (so you can then call functions on it from the scope of the child directive.

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

The above can be seen at http://plnkr.co/edit/poeq460VmQER8Gl9w8Oz?p=preview

A sibling directive is used similarly, but both directives on the same element:

<div directive1 directive2>
</div>

Used by creating a method on directive1:

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

And in directive2 this can be called by using the require option which results in the siblingController being passed to the link function:

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

This can be seen at http://plnkr.co/edit/MUD2snf9zvadfnDXq85w?p=preview .

The uses of this?

Parent: Any case where child elements need to "register" themselves with a parent. Much like the relationship between ngModel and ngForm. These can add certain behaviour that can affects models. You might have something purely DOM based as well, where a parent element needs to manage the positions of certain children, say to manage or react to scrolling.

Sibling: allowing a directive to have its behaviour modified. ngModel is the classic case, to add parsers / validation to ngModel use on inputs.


t
tomascharad

I don't know if this is out of standards but if you have all of your controllers on the same file, then you can do something like this:

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

As you can see indicatorsCtrl is calling the updateChart funcions of the other both controllers when calling updateCharts.


S
Smrutiranjan Sahu

You can inject '$controller' service in your parent controller(MessageCtrl) and then instantiate/inject the child controller(DateCtrl) using:
$scope.childController = $controller('childController', { $scope: $scope.$new() });

Now you can access data from your child controller by calling its methods as it is a service. Let me know if any issue.


L
LCJ

Following is a publish-subscribe approach that is irrespective of Angular JS.

Search Param Controller

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

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

Search Choices Controller

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


});

Event Manager

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

Global

var EM = myBase.EventManager;

K
Katana24

In angular 1.5 this can be accomplished by doing the following:

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

}());

This is the parent component. In this I have created a function that pushes another object into my productForms array - note - this is just my example, this function can be anything really.

Now we can create another component that will make use of 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;
    };  
  }

}());

Here the child component is creating a reference to the parents component function addNewForm which can then be bound to the HTML and called like any other function.


B
BanksySan

You can use $controller service provided by AngularJS.

angular.module('app',[]).controller('DateCtrl', ['$scope', function($scope){
  $scope.currentDate = function(){
    return "The current date is: " + new Date().toString(); 
  }
}]);

angular.module('app').controller('MessageCtrl', ['$scope', function($scope){

  angular.extend(this, $controller('DateCtrl', {
      $scope: $scope
  }));

  $scope.messageWithDate = function(message){
    return "'"+ message + "', " + $scope.currentDate;
  }

  $scope.action2 = function(){
    console.log('Overridden in ChildCtrl action2');
  }

}]);