Search code examples
javascriptangularjsdata-bindingobject-literal

How to bind properly to a service property in AngularJS?


I have a factory where I have a couple of predefined partners (it could be anything else, I thought it's an example that's easy to understand). On run time, we select the current partner (based on some logic I omitted here).

angular.module('myApp').factory('PartnersService', function ($location, $log) {

  var partners = {
    firstPartner: {
      name: 'Default Partner',
      id: 1 // just an extra property as example 
    },
    secondPartner: {
      name: 'Other Partner',
      id: 2
    }
  };

  // set default value
  var partner = partners.firstPartner;

  var initPartner = function () {
    // based on some logic (omitted), select partner
    partner = partners.secondPartner;
    $log.log("initPartner should have changed partner to " +  partner.name);
  };

  return {
    initPartner: initPartner,
    partners: partners,
    partner: partner,
  };
});

Then, I would like to access the partner as PartnersService.partner and see as it changes, e.g. from a controller:

angular.module('myApp').controller('myController',
  function ($scope, $log, PartnersService) {
    // PartnersService.partner is the default partner (firstPartner)
    PartnersService.initPartner();
    // After initPartner, PartnersService.partner is still
    // the default, but I expected it to change
});

I found some workarounds (in my opinion... are they workarounds?), but it feels unnatural for me, so I'd like to ask if there's a better way.

See my full, working example on JS Bin. I apologize if you find the example a bit lengthy, but I wanted to make sure Stack Overflow users understand my concerns and can point out if something is wrong with the way I think.

Workaround 1 (getter?):

angular.module('myApp').controller('myController', function ($scope, $log, PartnersService) {
  PartnersService.initPartner();
  var partner = PartnersService.getPartner();
  $log.log('I could use a getter: ' + partner.name);
});

angular.module('myApp').factory('PartnersService', function ($location, $log) {

  var getPartner = function () {
    return partner;
  };

  return {
    getPartner: getPartner,
    // ...
  };
});

Workaround 2 (nest in an object literal):

angular.module('myApp').controller('myController', function ($scope, $log, PartnersService) {
  PartnersService.initPartner();
  $log.log('or nest the partner in an extra object literal: '
     + PartnersService.extraObjectLiteral.partner.name);
});

angular.module('myApp').factory('PartnersService', function ($location, $log) {
  var partners = { /*...*/ }
  var extraObjectLiteral = {
    partner: partners.firstPartner
  };

  var initPartner = function () {
    // based on some logic (omitted), select partner
    extraObjectLiteral.partner = partners.secondPartner;
  };

  return {
    extraObjectLiteral: extraObjectLiteral,
    //...
  };
});

Solution

  • Change to:

    this.partner = partners.secondPartner;
    

    in your initPartner method. That will solve it.

    What you're really doing when you do

      var partners = { ... }
      var partner = partners.firstPartner;
    

    is, you're creating local objects in the class, but they are not exposed members of the class. And with the

      return {
        initPartner: initPartner,
        partners: partners,
        partner: partner
      };
    

    you create members of the class, and copy the values of the local variables to the class' members. In your initPartner method, you change the local object, but the class' object remain unchanged.