AngularJS multiple uses of Controller and rootScope

45,243

Solution 1

I would suggest to use a service that holds the menu and its methods. The service will update the menu which is referenced by the controller(s).

See a working plunker here: http://plnkr.co/edit/Bzjruq

This is the sample JavaScript code:

angular
 .module( 'sampleApp', [] )
 .service( 'MenuService', [ '$rootScope', function( $rootScope ) {

   return {
      menu: [ 'item 1' ],
      add: function( item ) {
        this.menu.push( item );
      } 
   };

 }])
 .controller( 'ControllerA', [ 'MenuService', '$scope', function( MenuService, $scope ) {

   $scope.menu = MenuService.menu;

   $scope.addItem = function() {
    MenuService.add( $scope.newItem );  
   };

 }]);

And the sample Html page:

<!DOCTYPE html>
<html>

  <head lang="en">
    <meta charset="utf-8">
    <title>Custom Plunker</title>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
    <script src="app.js"></script>
  </head>

  <body ng-app="sampleApp">

    <div ng-controller="ControllerA">
      <ul>
        <li ng-repeat="item in menu">{{item}}</li>
      </ul>
      <input type="text" ng-model="newItem" /><input type="submit" ng-click="addItem()" />
    </div>

    <div ng-controller="ControllerA">
      <ul>
        <li ng-repeat="item in menu">{{item}}</li>
      </ul>
    </div>

  </body>

</html>

Solution 2

Edit:

Here is the updated version plunker. it works in two controller.

Main idea is using service and broadcast to sync the data with the directive.

app.service('syncSRV', function ($rootScope) {
    "use strict";
    this.sync = function (data) {
        this.syncData = data;
        $rootScope.$broadcast('updated');
    };
});
app.controller('MainCtrl1', ['$scope', function ($scope) {
    }])
    .controller('MainCtrl2', ['$scope', function ($scope) {
    }]);
app.directive('sync',function (syncSRV) {
    "use strict";
    return {
        template: '<div><input ng-model="syncdata" type="text" /></div> ',
        controller: function ($scope, $element, $attrs) {
            $scope.$watch('syncdata', function (newVal, oldVal, $scope) {
                syncSRV.sync(newVal);
            }, true);
        }
    };
}).directive('dataview', function (syncSRV) {
    "use strict";
    return {
        template: '<div>Sync data : {{data}}</div> ',
        controller: function ($scope, $element, $attrs) {
            $scope.$on('updated', function () {
                $scope.data = syncSRV.syncData;
            });
        }
    };
});


<div ng-controller="MainCtrl1">
    <fieldset>
        <legend> Controller 1</legend>
        <div dataview></div>
        <div sync></div>
    </fieldset>
</div>
<div ng-controller="MainCtrl2">
    <fieldset>
        <legend> Controller 2</legend>
        <div dataview></div>
        <div sync></div>
    </fieldset>
</div>

Here is what I would do for this case.

I will create a directive for

<ul class="nav" ng-controller="Menu">
        <li ng-repeat="item in menu">
            <a href="{{item.href}}">{{item.title}}</a>
        </li>
</ul> 

so once item is updated, it will be updated in both directive.

small example

Solution 3

I just want to update and simplify the selected answer. It seems you can reduce this by deleting this line: $rootScope.$broadcast( 'MenuService.update', this.menu );

and this chunk:

$scope.$on( 'MenuService.update', function( event, menu ) {
 $scope.menu = menu;
});

The reason being, we are already using a Service, and that basically binds the two identical controllers, so no need to use $rootScope.$broadcast and add an observable.

Working plunk here: http://plnkr.co/edit/1efEwU?p=preview

You only need to link the service, when I refactor the code I was able to reduce it to 13 lines instead of 22.

Share:
45,243
Pete
Author by

Pete

I'm a web developer/full stack developer/programmer/nerd/internet person what ever you want to call me, I currently make websites and APIs, with CFML(ACF and Lucee), Node.js and Vue.js. I've done some Native iOS and Phonegap/Cordova iOS and Android work in the past. I'll can fix a computer, manage servers, monitor logs, build databases, optimize queries, shrink download sizes, speed up sites, write back end code, build front end interfaces, analyze user behaviours, get conversions and leads... just don't ask me to design anything. I'm a father, husband, 1st degree taekwondo black belt and Grade 2 guitarist who likes drawing, driving, gaming and all the usual sci-fi nerd stuff.

Updated on July 05, 2022

Comments

  • Pete
    Pete almost 2 years

    I want to use a controller on 2 seperated HTML elements, and use the $rootScope to keep the 2 lists in sync when one is edited:

    HTML

    <ul class="nav" ng-controller="Menu">
        <li ng-repeat="item in menu">
            <a href="{{item.href}}">{{item.title}}</a>
        </li>
    </ul>
    
    <div ng-controller="Menu">
        <input type="text" id="newItem" value="" />
        <input type="submit" ng-click="addItem()" />
        <ul class="nav" ng-controller="Menu">
            <li ng-repeat="item in menu">
                <a href="{{item.href}}">{{item.title}}</a>
            </li>
        </ul>    
    </div>
    

    JS

    angular.module('menuApp', ['menuServices']).
        run(function($rootScope){
            $rootScope.menu = [];
        });
    
    angular.module('menuServices', ['ngResource']).
        factory('MenuData', function ($resource) {
            return $resource(
                '/tool/menu.cfc', 
                {
                    returnFormat: 'json'
                },
                {
                    getMenu: {
                        method: 'GET',
                        params: {method: 'getMenu'}
                    },
                    addItem: {
                        method: 'GET',
                        params: {method: 'addItem'}
                    }
                }
            );
        });
    
    function Menu($scope, MenuData) {
    
        // attempt to add new item
        $scope.addNewItem = function(){
            var thisItem = $('#newItem').val();
    
            MenuData.addItem({item: thisItem},function(data){
                $scope.updateMenu();
            });
        }   
    
        $scope.updateMenu = function() {
            MenuData.getMenu({},function(data){
                $scope.menu = data.MENU;
            });         
        }
    
        // get menu data
        $scope.updateMenu();
    }
    

    When the page loads, both the UL and the DIV display the correct contents from the database, but when i use the addNewItem() method only the DIV gets updated.

    Is there a better way to structure my logic, or can I do something to make sure the $scope.menu in the UL gets updated at the same time?

    Here's an example of something similar: http://plnkr.co/edit/2a55gq

  • Pete
    Pete over 11 years
    Your example only has 1 controller, would that still work across 2 different controllers? e.g.: plnkr.co/edit/2a55gq. I'm restricted in how I can modify my page HTML
  • Pete
    Pete over 11 years
    That's the stuff, I was just looking into this after you posted that comment, thanks!
  • KajMagnus
    KajMagnus about 10 years
    Won't $scope.menu automatically be updated when MenuSerice.add is called, even without the $rootScope.$broadcast and $scope.$on stuff? Since $scope.menu points directly to MenuService.menu, it's not a copy of it.
  • David Riccitelli
    David Riccitelli about 10 years
    @KajMagnus thanks, I updated the answer and the related Plunker.
  • spaffy
    spaffy about 9 years
    So is it a very bad practice to attach same controller to many view elements? Or are there any times during development, that such solution is acceptable? I see no errors in webdev tools console, when running webapp which has such dirty hack (?).