7

I have an angular app that has 2 sections on the page.

1 section is the sidebar which gives a synopsis. Let's say it says:

Players 5 // {{ numOfPlayers }}

The code is an ajax call. I do not want to increment as this number could be increased by another call. I NEED to run the ajax call after to get the array length.

angular.module('app').controller('nav', function($scope,$http) {

  $http.get('/players').then(function(data) {
     $scope.numOfPlayers = data.players.length;
  });
});

Now in a completely seperate controller that is on the main page. A user can add a player. How do I have it so I can update the nav controller?

angular.module('app').controller('mainController', function($scope,$http) {
    $http.post(.....).then(function(data) {
    //update the numOfPlayers so the nav is updated.
    });
});
2
  • 2
    You can use a service to share data between controllers. Commented Jul 12, 2016 at 22:06
  • maybe have a call to the method in the nav controller so that the scope variable updates, ultimately updating your view via the two way binding. Commented Jul 15, 2016 at 12:51

8 Answers 8

5

Use a Service

You can use a service to hold the shared data and $watch changes:

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

app.service("playersService", function () {
    this.numOfPlayers = 0;
});

app.controller("navController", function ($scope, $http, playersService) {
    // Update the shared resource initial value with GET result
    // $http.get('/players').then(function(data) {
    //     playersService.numOfPlayers = response.data.length;
    // });
    playersService.numOfPlayers = 0;

    $scope.$watch(function () { return playersService.numOfPlayers; }, function (value) {
        $scope.numOfPlayers = value;
    });
});

app.controller("mainController", function ($scope, playersService) {
    $scope.addPlayer = function () {
        // POST and update the shared resource with result
        // $http.post(.....).then(function(data) {
        playersService.numOfPlayers++;
    }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="TestApp">
    <section ng-controller="navController">
        <h1>Nav</h1>
        Players {{ numOfPlayers  }}
    </section>
    <section ng-controller="mainController">
        <h1>Main</h1>
        <button ng-click="addPlayer()">
            Add player
        </button>
    </section>
</div>


Use Parent Controller

You can use a parent controller (say pageController) to hold the shared data:

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

app.controller("pageController", function ($scope) {
    $scope.numOfPlayers = null;
});

app.controller("mainController", function ($scope, $http) {
    $scope.addPlayer = function () {
        // POST and update the shared resource with result
        // $http.post(.....).then(function(data) {
        $scope.$parent.numOfPlayers++;
    };
});

app.controller("navController", function ($scope, $http) {
    // Update the shared resource initial value with GET result
    // $http.get('/players').then(function(data) {
    //     $scope.$parent.numOfPlayers = response.data.length;
    // });
    $scope.$parent.numOfPlayers = 0;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="TestApp" ng-controller="pageController">
   <section ng-controller="navController">
      <h1>Nav</h1>
      Players {{ numOfPlayers  }}
   </section>
   <section ng-controller="mainController">
      <h1>Main</h1>
      <button ng-click="addPlayer()">
         Add player
      </button>
   </section>
</div>


Side Notes:

In both approaches:

  1. Might be better to use players array as a shared resource. In the example I tried to keep it simple.

  2. Might be better to update the initial resource value from mainController and not the navController. In the example I tried to be consistent with your code.

Sign up to request clarification or add additional context in comments.

Comments

5
+75

First of all I would suggest to use best practices and use component instead of ng-controller.

So you have 2 components:

angular.module('app').component('nav', {});

and

angular.module('app').component('main', {});

Now you can share state data between them via services:

angular.module('app').service('PlayersService', function(){
  this.players = [];
  this.getAll() = () => {};
  this.add(player) = () => {};
});

Only one tricky part is that you need to watch in all your components for players change:

angular.module('app').component('nav', {
   controller: function($scope, PlayersService){

     PlayersService.getAll();
     $scope.$watch(() => PlayersService.players.length, (playersLength) => this.numOfPlayers = playersLength)
   }

});


angular.module('app').component('main', {
     controller: function($scope, PlayersService){

     //PlayersService.add(player);
     $scope.$watch(() => PlayersService.players.length, (playersLength) => this.numOfPlayers = playersLength)
   }
});

So in both cases scope property numOfPlayers gets updated.

Comments

2

Since you have two sections on one page my suggestion is to use a component or components instead of separate controllers. Example below:

angular
  .module('exampleApp', [])
  .controller('ExampleController', ExampleController);

function ExampleController() {
  var vm = this;
  vm.numPlayers = 0;
}

angular
  .module('exampleApp')
  .component('playerSummary', {
    bindings: {
      numPlayers: '<'
    },
    template: `<p>{{ $ctrl.numPlayers }}</p>`
  });

angular
  .module('exampleApp')
  .component('playerAddition', {
    bindings: {
      numPlayers: '='
    },
    controller: function() {
      function addPlayer() {
        this.numPlayers++;
      }
      this.addPlayer = addPlayer;
    },
    template: `<button type="button" ng-click="$ctrl.addPlayer()">+</button>`
  });
<!DOCTYPE html>
<html ng-app='exampleApp'>

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.5/angular.min.js"></script>
</head>

<body ng-controller="ExampleController as vm">
  <player-summary num-players="vm.numPlayers"></player-summary>
  <player-addition num-players="vm.numPlayers"></player-addition>
</body>

</html>

Not recommended, but you can also use $rootScope or catch and emit events.

You can also bind to a service property, example below, or directly to the service.

angular
  .module('exampleApp', []);

angular
  .module('exampleApp')
  .controller('FirstController', FirstController);

function FirstController(PlayerService) {
  var vm = this;
  vm.players = PlayerService.players;
}
FirstController.$inject = ['PlayerService'];
angular
  .module('exampleApp')
  .controller('SecondController', SecondController);

function SecondController(PlayerService) {
  var vm = this;
  vm.addPlayer = function() {
    PlayerService.addPlayer();
  }
}
SecondController.$inject = ['PlayerService'];
angular
  .module('exampleApp')
  .service('PlayerService', PlayerService);

function PlayerService() {
  var PlayerService = this;
  PlayerService.players = [];

  PlayerService.addPlayer = function() {
    PlayerService.players.push({});
  }
}
<!DOCTYPE html>
<html ng-app='exampleApp'>

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.5/angular.min.js"></script>
</head>

<body>
  <div ng-controller="FirstController as vm">
    <p>{{vm.players.length}}</p>
  </div>
  <div ng-controller="SecondController as vm">
    <button ng-click="vm.addPlayer()">+</button>
  </div>
</body>

</html>

As Khalil Malki mentioned, you can watch a service value

As Jaqen H'ghar mentioned, you can use a parent controller.

Comments

1

A better way to do this would be two have two directives:

  1. a page directive
  2. a nav directive

The nav directive would take some data as its input. The number of players in this case can be one of the inputs. Then, when you nest the nav directive inside the page directive, you can pass your data from page to nav and nav will get updated automatically when the values change:

html

<page></page>

JavaScript

app.directive('page', function() {
  return {
    restrict: 'E',
    controller: function($scope, playerSvc) {
      playerSvc.getPlayers.then(function(resp) {
        $scope.players = resp.data;
      });
    },
    template: '<header> blah</header> <nav player-count="players.length"></nav> <footer></footer>'
  };
});

app.directive('nav', function() {
  return {
    restrict: 'E',
    scope: { playerCount: '=' },
    template: '<div> player count: {{playerCount}} </div>'
  };

});

By doing this, you can isolate your directives (components) and create clear boundaries. Each component or directive would have a small responsibility. In this case, nav gets some data and displays them and is also responsible for navigation. The page directive provides global data for different components or directives on the page. You can use the same idea and decide who will add the player to the list of players. And because the nav directive is hooked up to the players.length, it will be updated automatically once the players are updated.

Comments

1

maybe have a call to the method in the nav controller so that the scope variable updates, ultimately updating your view via the two way binding

angular.module('app').controller('nav', function($scope,$http) {
 $rootScope.$on("CallMethodNavController", function(){
       $scope.navMethod();
    });
$scope.CallMethodNavController=function(){
$http.get('/players').then(function(data) {
 $scope.numOfPlayers = data.players.length;
}    
});
});

then in the second controller you call this method once a player is added like so:

        $rootScope.$emit("CallMethodNavController", {});

Comments

1

No need to create watches or events for this.

Use a service to get, store, update and share the array of players across the app.

Instead of creating a primitive variable that is the length ... store reference to the array in controller and in the view do {{players.length}} and let angular view watchers take care of updates.

angular.module('app').factory('playersService', function($http){
     // now have an array to share across app
     // and reference to addPlayer function
     var factory ={players:[], addPlayer: addPlayer };
     // load the players and add them to array
     $http.get('/players').then(function(response) {
         Array.prototype.push.apply(factory.players, response.data);
     });


     function addPlayer(player){
        return $http.post(url, player).then(function(resp){
           // add new player to shared array
           factor.push(resp.data);
        }
     }

    return factory;
});

Nav Controller

angular.module('app').controller('nav', function($scope,playersService) {
     $scope.players = playersService.players; // store full array reference
});

Nav View

 Number Players: {{players.length}} <!-- angular will automatically watch and update --> 

Other controller

angular.module('app').controller('mainController', function($scope,playersService) {

    $scope.newPlayer={};// bind to ng-model's in form
    $scope.saveNewPlayer = function(){
       playersService.addPlayer($scope.newPlayer).then(function(){
           alert('Number of players in nav will already be updted');
           // clear form
           $scope.newPlayer={};
       })
    });

}); 

Comments

1

use $emit and $on From main cintroller $rootScope.$broadcast('update','status'){ $http.post(.....).then(function(data) { //update the numOfPlayers so the nav is updated. });}

and in your nav controller use
$scope.$on('eventName', function (event, args) { $http.get('/players').then(function(data) {
 $scope.numOfPlayers = data.players.length;});});

$broadcast goes from parent to child so any change in parent controller will be reflected in child controller

Comments

1

You can do that using angular.factory()

or angular.service(); or $localStorage and or $sessionStorage.

First to make a global call instead of calling inside a controller:

Example:

angular.factory('updateVal', function(){
    var data;
    return {
    getPlayers: function(){
     return $http.get('/players').success(function(results){
        data = results;
       return data;
     });
    },
   setPlayers: function(val){
     if(val){ 
      data = val;
      return data;         
      } 
      else {       
       return data;
      }
    }
   }  
});

In your controller: First controller

angular.module('app').controller('nav', function($scope,updateVal){
 $scope.numOfPlayers = updateVal.getPlayers();

 $scope.$watch(function(){
    return updateVal.setPlayers().length > 0;
  }, function(){
    $scope.numOfPlayers = updateVal.setPlayers();
  })
})

Second controller:

angular.module('app').controller('mainController',       function($scope,$http,updateVal) {
    // this function update the players updateVal.getPlayers();
    $http.post(.....).then(function(data) {
    //update the numOfPlayers so the nav is updated.
     // after posting call this function:
      updateVal.setPlayers(data);
    });
});

Using angular.service :

This can be done :

angular.service('updatePlayers', function(){

var updatedPlayers;
this.setPlayers = function(args){

     updatedPlayers = args;
     } 
this.getPlayers = function(){
return updatedPlayers;
   }
 })

In your First controller:

angular.module('app').controller('nav', function($scope,$http,updatePlayers) {

  $http.get('/players').then(function(data) {
     updatePlayers.setPlayers(data.players.length);
     $scope.numOfPlayers = data.players.length;
  });
});

In the second controller:

angular.module('app').controller('mainController', function($scope,$http,updatePlayers) {
    $http.post(.....).then(function(data) {
    //update the numOfPlayers so the nav is updated.
     updatePlayers.getPlayers();
    });
});

EDITED to fix typo

3 Comments

In the factory example how do you update from the second controller?
Just to clarify you want to update the results in the controller 1 from inside of the controller two?
Yeah. I want to update the view that is associated with the first controller. But I want to ALSO be able to call the update function from the second controller, so the view in the first controller also can update.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.