Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS multiple uses of Controller and rootScope

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

like image 778
Pete Avatar asked Nov 01 '12 15:11

Pete


People also ask

How many rootScope can AngularJS application have?

An app can have only one $rootScope which will be shared among all the components of an app.

What is the use of rootScope in AngularJS?

All applications have a $rootScope which is the scope created on the HTML element that contains the ng-app directive. The rootScope is available in the entire application. If a variable has the same name in both the current scope and in the rootScope, the application uses the one in the current scope.

Can we have multiple controllers in AngularJS?

An AngularJS application can contain one or more controllers as needed, in real application a good approach is to create a new controller for every significant view within the application.

How many dollar rootScope in AngularJS JS application can have?

Each AngularJS application has exactly one root scope, but may have any number of child scopes.


3 Answers

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>
like image 178
David Riccitelli Avatar answered Oct 16 '22 00:10

David Riccitelli


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

like image 23
maxisam Avatar answered Oct 16 '22 02:10

maxisam


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.

like image 33
chriz Avatar answered Oct 16 '22 00:10

chriz