44

I am trying to call a method of second controller in first controller by using scope variable. This is a method in my first controller:

$scope.initRestId = function(){
        var catapp = document.getElementById('SecondApp');
        var catscope = angular.element(catapp).scope();
        catscope.rest_id = $scope.user.username;
        catscope.getMainCategories();
    }; 

I am able to set the value of rest_id but I cannot call getMainCategories for some reason. The console shows this error:

TypeError: Object # has no method 'getMainCategories'

Is there a way to call the above method?

Edit:

I used the following approach to load two apps at the same time;

angular.bootstrap(document.getElementById('firstAppID'), ['firstApp']);
angular.bootstrap(document.getElementById('secondAppID'), ['secondApp']);

I could definitely use a service here, but I wanted to know if there are any other options to do the same!

isherwood
  • 58,414
  • 16
  • 114
  • 157
Arif Nadeem
  • 8,524
  • 7
  • 47
  • 78

3 Answers3

64

The best approach for you to communicate between the two controllers is to use events.

Scope Documentation

In this check out $on, $broadcast and $emit.

In general use case the usage of angular.element(catapp).scope() was designed for use outside the angular controllers, like within jquery events.

Ideally in your usage you would write an event in controller 1 as:

$scope.$on("myEvent", function (event, args) {
   $scope.rest_id = args.username;
   $scope.getMainCategories();
});

And in the second controller you'd just do

$scope.initRestId = function(){
   $scope.$broadcast("myEvent", {username: $scope.user.username });
};

Edit: Realised it was communication between two modules

Can you try including the firstApp module as a dependency to the secondApp where you declare the angular.module. That way you can communicate to the other app.

Praveenram Balachandar
  • 1,587
  • 1
  • 13
  • 16
  • It would be `$scope.$broadcast` or `$scope.$emit` based on the heirarchy of `scope` in your html. – Praveenram Balachandar Oct 19 '13 at 17:13
  • 4
    in fact, you should use `$rootScope.$emit` or `$scope.$root.$emit`. Idem for $on and $broadcast – Utopik Oct 19 '13 at 17:29
  • If using `$rootScope` it should always be `$broadcast` since there is no scope above it as parent. – Praveenram Balachandar Oct 19 '13 at 17:30
  • I am combining the two modules into one because they are more dependent on each other, but the controllers have to be different. I am naming my event as "OnChange" and used your solution but the event is not fired:( – Arif Nadeem Oct 19 '13 at 17:34
  • If you use `$rootScope.$on`, `$broadcast` is useless. example http://jsfiddle.net/9Ymvt/627/ – Utopik Oct 19 '13 at 17:38
  • Ya I agree, that applies only to `$broadcast`, `$rootScope.$on` is something I would strongly suggest not to use. – Praveenram Balachandar Oct 19 '13 at 17:40
  • 1
    @ArifNadeem I'll send you a fiddle with working solution shortly. – Praveenram Balachandar Oct 19 '13 at 17:41
  • 6
    Here is a fiddle with `$broadcast` example. [http://jsfiddle.net/XjAfr/](http://jsfiddle.net/XjAfr/) – Praveenram Balachandar Oct 19 '13 at 18:03
  • @PraveenramBalachandar take a look at the angular source and compare $broadcast and $emit ;) – Utopik Oct 19 '13 at 18:13
  • @PraveenramBalachandar, it worked now:) What would be your suggestion for my app should I use this message passing approach or should I consider converting it to a service? – Arif Nadeem Oct 19 '13 at 18:15
  • 2
    @ArifNadeem If you do not want to over engineer and its a small application, I would suggest stick to events. If you are seeing too many events creeping in to facilitate communication between controllers, stop everything you are doing and write services. :) – Praveenram Balachandar Oct 19 '13 at 18:34
  • @Utopik I'm sorry I looked at this: [rootScope.js](https://github.com/angular/angular.js/blob/14438058da39c3e523f420549074934ca5881b09/src/ng/rootScope.js) and found pretty much what I'd expected, `$broadcast` fires event to every `scope` under it and `$emit` to `scope.$parent`. And while using `$rootScope.$broadcast` would send event to all scopes in app, `$rootScope.$emit` would only to itself i.e. `$rootScope`. Was there something specific you wanted me to discover on it? :) – Praveenram Balachandar Oct 19 '13 at 18:38
  • thanks, I am voting your answer as you answered it according to my need, the other answers were excellent but this approach makes sense for a small application which is my current app. – Arif Nadeem Oct 19 '13 at 18:41
  • 2
    And here is a fiddle which will make your original approach with 2 modules work: [2 module fiddle](http://jsfiddle.net/5HPHW/) :) – Praveenram Balachandar Oct 19 '13 at 18:52
  • @PraveenramBalachandar, exactly. `$broadcast` is like `$emit`, it sends the event to the parent, but also to **each scopes**. It will be a problem in large apps. When I have experienced the problem, I read the source. It was an eye opener. The root scope is nothing more than a scope. `var $rootScope = new Scope();`. Rootscope is just a scope. Hence `$rootScope.$on` === `$scope.$on`. (right, "==="). The main difference ? the array of listeners is not the same. Just a game of references. So why "$rootScope.$on is something I would strongly suggest not to use.`. I'm interested (for real) – Utopik Oct 21 '13 at 13:04
  • @Utopik I agree with you that `$rootScope` is just a `Scope` and that both `$broadcast` and `$emit` fire the events to the current `$scope` as well, my point was more about having such global events as a practice, event handlers should act at a more granular level than at an `app` level. With need for such `"global"` event handlers application should be designed with utmost care watching out for firing the wrong event even by mistake. Such global events, and lot of events in general add constraints on free flowing development. – Praveenram Balachandar Oct 21 '13 at 14:04
24

Each controller has it's own scope(s) so that's causing your issue.

Having two controllers that want access to the same data is a classic sign that you want a service. The angular team recommends thin controllers that are just glue between views and services. And specifically- "services should hold shared state across controllers".

Happily, there's a nice 15-minute video describing exactly this (controller communication via services): video

One of the original author's of Angular, Misko Hevery, discusses this recommendation (of using services in this situation) in his talk entitled Angular Best Practices (skip to 28:08 for this topic, although I very highly recommended watching the whole talk).

You can use events, but they are designed just for communication between two parties that want to be decoupled. In the above video, Misko notes how they can make your app more fragile. "Most of the time injecting services and doing direct communication is preferred and more robust". (Check out the above link starting at 53:37 to hear him talk about this)

Blaskovicz
  • 6,122
  • 7
  • 41
  • 50
KayakDave
  • 24,636
  • 3
  • 65
  • 68
  • thank you, I've edited my question, I am curious to know if my problem can be solved without making the current approach as a service? – Arif Nadeem Oct 19 '13 at 16:59
  • 1
    You can, I updated my answer with some great discussion from one of the author's of angular that I found very helpful if you'd like to keep with the "angular way". But do check out events- they are good to also know about. – KayakDave Oct 19 '13 at 17:40
24

Here is good Demo in Fiddle how to use shared service in directive and other controllers through $scope.$on

HTML

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

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

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

<my-component ng-model="message"></my-component>

JS

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

By the same way we can use shared service in directive. We can implement controller section into directive and use $scope.$on

myModule.directive('myComponent', function(mySharedService) {
    return {
        restrict: 'E',
        controller: function($scope, $attrs, mySharedService) {
            $scope.$on('handleBroadcast', function() {
                $scope.message = 'Directive: ' + mySharedService.message;
            });
        },
        replace: true,
        template: '<input>'
    };
});

And here three our controllers where ControllerZero used as trigger to invoke prepForBroadcast

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

The ControllerOne and ControllerTwo listen message change by using $scope.$on handler.

Maxim Shoustin
  • 77,483
  • 27
  • 203
  • 225
  • What a great simple example of how to use a simple service to communicate between controllers! – Darryl Sep 25 '14 at 16:08
  • wonderful .. even when I have set the value in my service from controller one I need to make sure controller two knows this and update accordingly. You answer worked like a charm. – Amarnath Nov 20 '15 at 07:45
  • Dmn, I'm using Angular for years now and this is the most beautiful implementation I came across. **mySharedService** is going to be renamed and it will take a place of honor in my collection I'm using in all my projects. Bit of polishing _(ie. I believe the messages would need an envelope)_ and it can handle 98% communication requirements. – Skiper Skiprovic Sep 30 '16 at 12:25