Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to update factory in one controller, and have another controller notice that it has been updated?

I have a page where one controller shows all of the logged in user's teams, and another controller allows the user to update a team. When the user updates the team name, I want the controller that displays all the teams to notice that a team has been updated and update it's variable accordingly.

I've been googling around and it seems there's lots of questions and lots of different ways to do this. Ideally i'd like to be able to just update a factory variable and all the controllers would notice that the value has been updated. Not sure if that is how angular works though.

Example:

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

...

// This gets the teams that a user is associated with
myezteam.factory('teams', function($http) {

    // Get the teams associated with the logged in user
    return {
        getTeams: function(callback) {
            $http.get(baseUrl+'v1/teams/all' + apiKey)
                .success(function(response) {
                    callback(response);
                });
        }
    }

});

Controller which gets all the teams

// This controller is used to set the user profile links
myapp.controller('TemplateProfileController', ['$scope', '$http'', 'teams', function($scope, $http, teams) {

    // Gets all of a user's teams from the "teams" factory
    getTeams = function() {
        teams.getTeams(function(response) {
            $scope.teams = response;    
        });
    }

    $scope.teams = getTeams();  // Call on page load

}]);

Controller which handles the editing of a team

// Controller for editing a team
myapp.controller('EditTeamController', ['$scope', '$http', '$routeParams', 'teams', function($scope, $http, $routeParams, teams) {

    // Get the team that we're editing
    getTeam = function() {    
        $http.get(baseUrl+'v1/teams/' + $routeParams.id + apiKey)
            .success(function(response) {
                $scope.team = response;
            });
    }

    // Update the team and refresh the list of all teams
    $scope.updateTeam = function() {        
        $http.post(baseUrl+'v1/teams' + apiKey, $scope.team)
            .success(function(response) {
                // NEED TO SOMEONE TRIGGER THE TempalteProfileController to get the teams from the factory again
            })
    } 

    getTeam();  // Call on page load;

}]);
like image 714
Catfish Avatar asked Dec 30 '13 21:12

Catfish


2 Answers

This is actually very simple to do as long as you understand the difference between variables and objects.

Say I have a service that keeps track of a counter and provides a way to increment it:

app.factory("mySharedService", function(){
  var values = {
    mySharedValue: 0
  }; 

  return{
    getValues: function(){
      return values;
    },

    incrementValue: function(){
      values.mySharedValue++;
    }
  };
});

Notice that rather than just declaring a simple variable, I declare an object and hang my actual value from the object. This object reference will remain the same for the lifetime of the service and only the values inside of it will change.

This means any controller who calls getValues() will receive the same object reference and can use that in their scope:

app.controller('Controller1', function($scope, mySharedService) {
  $scope.values = mySharedService.getValues();
});

app.controller('Controller2', function($scope, mySharedService) {
  $scope.values = mySharedService.getValues();
});

app.controller('Controller3', function($scope, mySharedService) {
  $scope.values = mySharedService.getValues();
});

Whenever the value in mySharedValue changes, all controllers will see it equally (because they are all sharing the same object reference). This means that any bindings in that controller will update instantly, and you can use $scope.$watch() just like you do with any other scope variable.

Here is an example plunker:

http://plnkr.co/edit/FA3MbfQQpiOtp5mGqqAq?p=preview

EDIT:

If you want to preserve encapsulation and only able to edit value through the provided methods you could do something like:

app.factory("mySharedService", function(){
  var mySharedValue = 0;

  var values = {
    getMySharedValue: function(){
       return mySharedValue;
    }
  }; 

  return{
    getValues: function(){
      return values;
    },

    incrementValue: function(){
      mySharedValue++;
    }
  };
});
like image 164
Daniel Tabuenca Avatar answered Oct 05 '22 22:10

Daniel Tabuenca


The easiest thing to do would be to put teams on the $rootScope, that way it's available to all controllers that will use teams. I find this is the best solution for CRUD apps where you have a "list" controller and a "new" controller, "edit", etc...

It took me a while to get comfortable with it, as I see using $rootScope like using global variables; however, I heard Misko (creator of angularjs) answer someone's question once during a presentation by saying "why not just put it on the rootScope". He didn't seem as concerned as I was.

The main thing is - when you put something on the $rootScope it makes it just makes the code cleaner - I started with broadcasting and listening to events and the code was way messier.

Ultimately, the way I see it, if your page / app's sole purpose is to show teams - and you have multiple controllers dealing with it - putting it on the $rootScope makes sense.

So long story short, replace $scope.teams with $rootScope.teams and make sure you include $rootScope in the controller constructor functions for dependency injection.

like image 28
xdotcommer Avatar answered Oct 05 '22 22:10

xdotcommer