cupofnestor February 2016

Why isn't $scope updated when bound properties of services are updated by other services?

I have one service for handling data, and one for logic.

app.service('DataService', function() {
  this.stuff = [false];
  this.setStuff = function(s){
    this.stuff = angular.copy(s);
  }
});

The data service has a set function and a data property.

app.service('LogicService', function(DataService, $http) {
  DataService.setStuff(["apple", "banana"]);
  $http.get("./data.json").then(function(res){
    DataService.setStuff(res.data.stuff);
  });

});

I am assigning a property of the data service to the controller for binding to the DOM.

app.controller('MainCtrl', function($scope, DataService, LogicService  ) {
  $scope.message = "Hello, World!";
  $scope.stuff = DataService.stuff;

  //This is the only way I could get it to work, but isn't this JANKY?
  //$scope.$watch(
  //  function(){
  //    return DataService.stuff
  //  },
  // function(n,o){
  //   $scope.stuff = n;
  // })

})

If I 'seed' the data service when the logic service is instantiated, and then later update it following an $http call, the DOM reflects the 'seeded' or initial value, but does not update.

Is there something fundamental I am missing in my understanding of the digest loop?

If I add a $watch function in my controller, all is well, but this seems yucky.

//FIXED//

@scott-schwalbe 's method of using Object.asign() works nicely, preserves my original structure, and is one line.

this.setStuff = function(s){
   Object.assign(this.stuff, s);
}

Working Plunker

(sorry for titlegore)

Answers


Scott Schwalbe February 2016

If your data property is an object and is binded to the scope, then the scope will update whenever the object changes as long as you don't dereference it (eg data = x). Are you reassigning data object on the $http call?

An alternative to your current code to keep the reference using Object.assign

app.service('DataService', function() {
  this.stuff = [false];
  this.setStuff = function(s){
    Object.assign(this.stuff, s);
  }
});


gr3g February 2016

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

app.controller('MainCtrl', function($scope, DataService) {
  $scope.message = "Hello, World!";

   //Get stuff data from your service, this way you stuff lives in your service  
   //And can be accessed everywhere in your app.   
   //It also makes your controller thin. Which is the top priority
   $scope.stuff = DataService.getStuff();
   //Or async
   DataService.getStuffAsync()
      .then(function(val){
       $scope.asycStuff = val;
   });

    this.clickFromAButton = function(){
      DataService.setStuff(["apple", "banana"]);
    };
});


app.service('DataService', function() {
  this.stuff = [false];
  this.asyncStuff;

  this.setStuff = function(s){
    this.stuff = angular.copy(s);
  };

  this.getStuff = function(){
      return this.stuff;
  };

  this.getStuffAsync = function(){
     //If i already fetched the data from $http, get it from the service.  
     return this.asyncStuff || $http.get("./data.json").then(function(res){
        //When i fetch it for the first time I set the data in my service
        this.asyncStuff  = res.data;
        //and I return the data
        return res.data;
     });
  };

});

This is a good 'pattern' to follow ;)


georgeawg February 2016

Instead of putting "stuff" on scope. Put your DataService object on scope.

app.controller('MainCtrl', function($scope, DataService, LogicService  ) {
  $scope.message = "Hello, World!";
  $scope.DataService = DataService;
  //$scope.stuff = DataService.stuff;

HTML

  <body ng-controller="MainCtrl">
    {{DataService.stuff}}
  </body>

The $interpolate service will automatically places a $watch on DataService.stuff. Thus there is no need to do it inside your controller.

The DEMO on PLNKR.

Post Status

Asked in February 2016
Viewed 1,579 times
Voted 6
Answered 3 times

Search




Leave an answer