Sharing $scope between controllers with AngularJs


Angular creates one $scope object for each controller. We also have a $rootScope accesible from every controllers. But, can we access to one controller’s $scope from another controller? The sort answer is no. Also if our application needs to access to another controller’s $scope, we probably are doing something wrong and we need to re-think our problem. But anyway it’s possible to access to another controller’s $scope if we store it within a service. Let me show you and example.

Imagine this little example:

<!doctype html>
<html ng-app="app">
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.18/angular.js"></script>
    <script src="app.js"></script>
</head>
<body>

<div ng-controller="OneController">
    <h2>OneController</h2>
    <button ng-click="buttonClick()">
        buttonClick on current scope
    </button>
</div>

<div ng-controller="TwoController">
    <h2>TwoController</h2>
    <button ng-click="buttonClick()">
        buttonClick on current scope
    </button>
</div>
</body>
</html>

As we can see we define two controllers: “OneController” and “TwoController”.

That’s the application:

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

app.controller('OneController', function ($scope) {
    $scope.variable1 = "One";

    $scope.buttonClick = function () {
        console.log("OneController");
        console.log("$scope::variable1", $scope.variable1);
    };
});

app.controller('TwoController', function ($scope) {
    $scope.variable1 = "Two";

    $scope.buttonClick = function () {
        console.log("TwoController");
        console.log("$scope::variable1", $scope.variable1);
    };
});

If we need to access to another controller’s $scope we need to store those scopes within a service. For example with this minimal service:

app.factory('Scopes', function ($rootScope) {
    var mem = {};

    return {
        store: function (key, value) {
            mem[key] = value;
        },
        get: function (key) {
            return mem[key];
        }
    };
});

And now we need to store the $scope in the service:

app.controller('OneController', function ($scope, Scopes) {
    Scopes.store('OneController', $scope);
    ...
});
app.controller('TwoController', function ($scope, Scopes) {
    Scopes.store('TwoController', $scope);
    ...
});

And now we can access to another’s $scope

Here the full example:

<!doctype html>
<html ng-app="app">
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.18/angular.js"></script>
    <script src="app.js"></script>
</head>
<body>

<div ng-controller="OneController">
    <h2>OneController</h2>
    <button ng-click="buttonClick()">
        buttonClick on current scope
    </button>
    <button ng-click="buttonClickOnTwoController()">
        buttonClick on TwoController's scope
    </button>
</div>

<div ng-controller="TwoController">
    <h2>TwoController</h2>
    <button ng-click="buttonClick()">
        buttonClick on current scope
    </button>
    <button ng-click="buttonClickOnOneController()">
        buttonClick on OneController's scope
    </button>
</div>
</body>
</html>

and app.js

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

app.run(function ($rootScope) {
    $rootScope.$on('scope.stored', function (event, data) {
        console.log("scope.stored", data);
    });
});
app.controller('OneController', function ($scope, Scopes) {

    Scopes.store('OneController', $scope);

    $scope.variable1 = "One";

    $scope.buttonClick = function () {
        console.log("OneController");
        console.log("OneController::variable1", Scopes.get('OneController').variable1);
        console.log("TwoController::variable1", Scopes.get('TwoController').variable1);
        console.log("$scope::variable1", $scope.variable1);
    };

    $scope.buttonClickOnTwoController = function () {
        Scopes.get('TwoController').buttonClick();
    };
});
app.controller('TwoController', function ($scope, Scopes) {

    Scopes.store('TwoController', $scope);

    $scope.variable1 = "Two";

    $scope.buttonClick = function () {
        console.log("TwoController");
        console.log("OneController::variable1", Scopes.get('OneController').variable1);
        console.log("TwoController::variable1", Scopes.get('TwoController').variable1);
        console.log("$scope::variable1", $scope.variable1);
    };

    $scope.buttonClickOnOneController = function () {
        Scopes.get('OneController').buttonClick();
    };
});
app.factory('Scopes', function ($rootScope) {
    var mem = {};

    return {
        store: function (key, value) {
            $rootScope.$emit('scope.stored', key);
            mem[key] = value;
        },
        get: function (key) {
            return mem[key];
        }
    };
});

You can also see it running here

Advertisements

About Gonzalo Ayuso

Web Architect. PHP, Python, Node, Angular, ionic, PostgreSQL, Linux, ... Always learning.

Posted on September 8, 2014, in AngularJS, js, Technology and tagged , . Bookmark the permalink. 17 Comments.

  1. Having in mind that you did this article with your best intentions I have to say that your idea is not good. As you say, you shouldn’t share `$scope` in any case that said, even a workaround to do that is a bad idea. Leaving that aside, your idea has a big big issue. The factory is a singleton so what you are doing is basically pushing and pushing scopes all the way down creating a major memory leak. You never clean those $scopes so at the end of the day it can contain hundred of instances, thousand of them.

    You can listen to `$destroy` event and do some cleanup, but that is extra code you need to do, you need to update the factory, and you can always forget in one controller to do the cleanup.. At the end of the day, it gives more problems than joy.

    Said that, please, just create a service to share what you need (never ever scopes) and use it where you need it.

    Cheers.

    • Agree. Maybe the post is not very clear. The idea of this post something like: you cannot access to one controller’s $scope from another $scope. OK but $scope is just a js object so if we store this object in a service (service is just a singleton) we can access to this object from another controller. It works, but as you said it may have performance issues.

      Anyway I don’t see the memory leak here (the service stores the object with the same key. It not appends a new one) but I’m quite sure that you are right.

      For example in this example scope.stored is raised only once. OK. If we use ng-route in a medium-big app scope.stored event will be raised more than once, but even then Scopes service will stores them with the same kay.

      If I, for example, register a new new event within the even dispatcher in each controller and I forget to listen to $destroy an un-register events I will have a problem, but here I don’t see the real problem.

    • Can you please backup your comments with working code, that solves the use case Gonzalo is solving. Thanks

  2. Check this example: http://plnkr.co/edit/QSTYNqlLTmK6tSsghC4b?p=preview

    As you see, every time I go to `/` I create a new $scope (which has a new $id) so every time you move to different views and you start generating $scopes, all of them are going to be saved, so if in my demo I save that controller scope in your factory, I create a new copy of the $scope every time I hit it.

  3. new $scope is created, but it doesn’t generate a new property within the Mem object (inside Scopes service)

    http://plnkr.co/edit/oBKtP359Mb8c3NgtWIFA

    OK. The object is stored again and again, but I don’t see the mem issue here, even changing scopes thousand of times. Something different happens if we push $scope to Scopes service.

    I know you’re right but I want to understand why ūüôā

  4. Because every time you change your route, a new scope is “cached” in your factory. If that route has multiple controllers (like your example) that is X*changes. So it could end having hundred of scopes inside the factory.

    The main idea on angular is that when an $scope is not needed, you just remove it because it can contain a reference to something that can’t be collected by the GC until the $scope is destroyed and you don’t destroy it.

  5. Ok. So the only problem is the waste of memory storing the whole $scope. But AFAIK we don’t need to listen $destroy event here.

    Of course store the whole $scope isn’t a good idea. As I said before, if we need to do that probably we aren’t facing the problem in the right way. We need to create an specific service to persist data between scopes. The only purpose of this post is something like: hey! $scope is nothing more than a js object. It hasn’t any magic behind and we can work with is as we work with js objects. And one service in AngularJS is just a singleleton and we can create a Service provider without problems

  6. Thanks! It really helped me out alot…

  7. Hi,When F5.One error will be showed.Can not found service

  8. This might be helpful. Communication using $emit(),$broadcast() and $on with examples

    http://blog.logiticks.com/communication-between-controllers-in-angularjs-emitonbroadcast/

  9. It’s great work sir,,,, that is i need, some search i did not fount any solution…

    thanks for simple logic for greatworks..

  10. Angularjs docs give the usage of $controller service as: $controller(constructor, locals);

    You can create common functions which are to be executed on $scope into one controller may be named ‘CommonCtrl’

    angular.module(‘app’,[]).controller(‘CommonCtrl’, [‘$scope’, function($scope){
    var self = this;
    $scope.stuff1 = function(){

    }

    $scope.stuff2 = function(){

    }
    self.doCommonStuff = function(){
    // common stuff here
    $scope.stuff1();
    $scope.stuff2();

    };
    return self;
    }]);

    And inject this controller in other controllers let say ‘TestCtrl1’ like

    angular.module(‘app’,[]).controller(‘TestCtrl1’, [‘$scope’,’$controller’, function($scope, $controller){
    var commonCtrl = $controller(‘CommonCtrl’,{$scope: $scope}); // passing current scope to commmon controller
    commonCtrl.doCommonStuff();
    }]);

    Here, the in second argument of $controller service, we are passing dependencies that are required by CommonCtrl. So the doCommonStuff method will use TestCtrl1 controller’s scope.

  11. brijesh K Singh

    Hi, I am learning angular and at very starting stage. I have stuck i problem discribed below-
    1- i am desing a web page and have use the ngRoute. On a page I am displayin the data from an API.
    2- Now i want when any data displayed through API, a new page shoud open with more detail of that particular data.

    how I can do this??

  12. I like this post very much. It solves a use case in a very simple way. I read some comments that disagree with this approach, but don’t provide working code as to how to do it better, so they are not nearly as helpful as your approach. Thank your for sharing.

  1. Pingback: Compartilhando $scope entre controladores com AngularJs -

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: