1

So, I've got a counter ($scope.counter) that I increment on ng-click. The problem I'm having is that I want to increment it from two different controllers, keeping the counter value in sync.

controllerA.js

.controller('controllerA', controllerA);
  function controllerA($scope) {
    $scope.counter = 0;

    function incrementCounter() { 
      $scope.counter = $scope.counter + 1;
    }
    ...

controllerB.js

.controller('controllerB', controllerB);
  function controllerB($scope) {
    $scope.counter = 0;

    function incrementCounter() { 
      $scope.counter = $scope.counter + 1;
    }
    ...

When I call the incrementCounter() on 'controllerA' it updates the counter on 'controllerA', but not on 'controllerB' and vice versa.

Is there a proper way to keep these in sync, no matter which controller I call the incrementCounter() function from?

Any help is appreciated. Thanks in advance!

2
  • Create a service that contains the counter variable and/or function and inject that service into both controllers Commented Oct 28, 2015 at 20:57
  • In these type of scenarios you might be tempted to use $rootScope, which is not good practice rather use service as suggested be @jbrown Commented Oct 28, 2015 at 21:04

5 Answers 5

1

A service is a good way to share the counter variable. If you want to avoid watches to keep them in sync, make sure you define the variable as a property of an object on the service. You can set the value to null initially if you want to populate it with a $http callout as per your comment.

app.service('MyService', ['$http', function($http) {
  var service = {
    counter: {
      value: null
    },
    incrementCounter: incrementCounter,
    fetchTotal: fetchTotal
  }

  return service;

  function incrementCounter(){
    service.counter.value++;
  }

  function fetchTotal(url, p) {
    $http.get(url, { params: p })
      .then(function(response) {
        service.counter.value = response.data.meta.total;
      }, function(error) {
        console.log("error occurred");
    });
  }
}]);

Then assign the counter object as a property on $scope in your controllers, and call the service to do the $http callout:

app.controller('Controller1', function ($scope, MyService) {
  $scope.counter = MyService.counter;

  $scope.incrementCounter = function(){
    MyService.incrementCounter();
  }

  fetchTotal('/test', '');
});

Assigning the counter object as a property on $scope ensures that ensure two-way binding is intact in the view. You can use ng-if so you don't render anything until the $http call is complete and the counter is initialised.

<div ng-controller="Controller1">
  <div ng-if="counter.value">
    {{counter.value}}
    <span ng-click="incrementCounter()">increment</span>  
  </div>
</div>

Fiddle with a mocked $httpBackend

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

4 Comments

What if I want to do a $http.get request to retrieve my counter total from an API. Like so: fetchTotal: function(p) { return $http.get(url, { params: p }) .then(function(response) { var total = response.data.meta.total; return total; }, function(error) { console.log("error occured"); }) }, how would be best to increment on that total variable?
Am I able to put my params in my controller in this example?
Yes, fiddle updated to show that. I'll update the answer too.
Fantastic answer! Thank you. Very helpful in helping me split up my code into a service.
1

There are two solutions.

  1. using $rootScope instead of $scope.
  2. Having a parent controller and define $scope.counter = 0 in it, then in your child controllers(A,B) that are used in this parent just update the $scope.counter value.

1 Comment

But both of them are not recommended way. If service is used, code is more maintainable and testable. And, if complexity for counter increases in future, one can easily abstract its implementation from both the controllers
1

This is where services come in handy.

egghead.io has a nice video on this: https://egghead.io/lessons/angularjs-sharing-data-between-controllers

He talks a lot about ui-router as well, but it also shows how you can use a service.

UPDATE

If you want the value to update instantaniously in both controllers you can watch the service.

http://jsfiddle.net/jkrielaars/1swyy6re/2/

app.service('counterService', function() {
    this.counter = 0;
    this.addOne = function(){
        this.counter++;
    }
    this.getCounter = function(){
        return this.counter;
    }
});

//Controllers
app.controller("controller1",function($scope, counterService){
    $scope.counterService = counterService;
    $scope.$watch('counterService.getCounter()', function (newVal) {
      $scope.counter = newVal;
    });

    $scope.addOne = function(){
       counterService.addOne();
   }
})

app.controller("controller2",function($scope, counterService){
    //same internals as controller 1
})

In your views you can now have both controllers that will update simultaniously

<div ng-controller="controller1">
    counter value: {{counter}}
    <button ng-click="addOne()">add 1</button>
</div>

<div ng-controller="controller2">
    counter value: {{counter}}
    <button ng-click="addOne()">add 1</button>
</div>

1 Comment

It's kind of a similar example. I'm displaying two views on the same page though, each with their own controller. I've got a service setup which is providing each controller with the SAME counter number. However, when I do an ng-click on viewA (controller A), it only updates the counter inside view A and not the counter inside view B.
0

You also can use the broadcast of events to update the value in other controller. Here is how it can be done:

app.controller('MainCtrl', function($scope, $rootScope) {
  $scope.name = 'World';
  $scope.counter = 0;
  $scope.incrementCounter = function() {
    $scope.counter++;
    $rootScope.$broadcast('incrementedInController1', $scope.counter);
  }
  $scope.$on('incrementedInController2', function(event, newValueCounter) {
    $scope.counter = newValueCounter;
  });
}).controller('MainCtrl2', function($scope, $rootScope) {
  $scope.name2 = 'World2';
  $scope.counter = 0;
  $scope.incrementCounter = function() {
    $scope.counter++;
    $rootScope.$broadcast('incrementedInController2', $scope.counter);
  }
  $scope.$on('incrementedInController1', function(event, newValueCounter) {
    $scope.counter = newValueCounter;
  });
});

Comments

0

You can use $rootScope which can be accessed from all controllers but it's not recommended because of debuging issues.

.controller('controllerA', controllerA);
  function controllerA($rootScope) {
    $rootScope.counter = 0;
}
.controller('controllerB', controllerA);
  function controllerB($rootScope) {
    $rootScope.counter = 1;
}

if you are using something common between all controllers it's recommended to use Service or Factories instead

here is a sample when to use it : http://viralpatel.net/blogs/angularjs-service-factory-tutorial/

Comments

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.