34

I'm building an ecommerce site (based on shopify) and I'm using multiple small angularjs apps to handle things such as a quick shopping cart, wishlists, filtering products and a few other smaller items. I initially used one big application (that had routing and everything), but it was a bit to restrictive when I didn't have a full REST API.

There are a couple of services that I would like to share between the angular apps (the cart service, so I can have a quick add button that will reflect in the mini-cart and such), but I'm not sure of the best way (if there is a way) to go about this. Just sharing a module with the service doesn't keep the same state across the apps.

I tried my hand at it, but I it doesn't seem to update state between both apps. The following is the javascript I tried using. It's also on jsfiddle with accompanying html: http://jsfiddle.net/k9KM7/1/

angular.module('test-service', [])
  .service('TestService', function($window){
    var text = 'Initial state';

    if (!!$window.sharedService){
      return $window.sharedService;
    }

    $window.sharedService = {
      change: function(newText){
        text = newText;
      },
      get: function(){
        return text;
      }
    }

    return $window.sharedService;
  });

angular.module('app1', ['test-service'])
  .controller('App1Ctrl', function($scope, TestService){
    $scope.text = function(){ return TestService.get() }
    $scope.change = function(){ TestService.change('app 1 activated') }
  });

angular.module('app2', ['test-service'])
  .controller('App2Ctrl', function($scope, TestService){
    $scope.text = function(){ return TestService.get() }
    $scope.change = function(){ TestService.change('app 2 activated') }
  });

var app1El = document.getElementById('app1');
var app2El = document.getElementById('app2');

angular.bootstrap(app1El, ['app1', 'test-service']);
angular.bootstrap(app2El, ['app2', 'test-service']);

Any help would be appreciated

Tom Brunoli
  • 3,436
  • 9
  • 36
  • 54

2 Answers2

25

The sharedService is being shared, but one angular app doesn't know that something updated in the other app so it doesn't kick off a $digest. You have to manually tell the $rootScope of each application to start a $digest by calling $rootscope.$apply()

Fiddle: http://jsfiddle.net/pvtpenguin/k9KM7/3/

  angular.module('test-service', [])
  .service('TestService', function($rootScope, $window){
    var text = 'Initial state';
    $window.rootScopes = $window.rootScopes || [];
    $window.rootScopes.push($rootScope);

    if (!!$window.sharedService){
      return $window.sharedService;
    }

    $window.sharedService = {
      change: function(newText){
        text = newText;
        angular.forEach($window.rootScopes, function(scope) {
          if(!scope.$$phase) {
              scope.$apply();
          }
        });
      },
      get: function(){
        return text;
      }
    }

    return $window.sharedService;
  });
Matt York
  • 15,981
  • 7
  • 47
  • 51
  • That works nicely. How would you propose I do the $window.rootScopes thing with multiple shared services? I would presume it would double up if I wasn't careful – Tom Brunoli May 24 '13 at 00:52
  • Ah don't worry, I figured out a good way to do it - put it in a run block in the apps, not the shared modules – Tom Brunoli May 24 '13 at 03:44
  • @TomBrunoli can you elaborate on what you did? How did you move the service code from the shared modules to the apps' run block without error? – kevin11 Dec 18 '13 at 04:55
  • @kevin11 I wish I could help, but I no longer have the code. We ended up re-factoring everything to work as one app. I probably should've posted my solution when I had it… – Tom Brunoli Dec 18 '13 at 05:01
  • I know this is really old, but not sure how this is working. When I try this each windowService rootScope collection simply has the one rootScope added by each module and no other rootScope elements for the other modules. – Jacob Barnes Mar 09 '21 at 18:08
3

I was trying to solve a similar problem. Sharing factories between apps running in different iFrames. I wanted any $apply() in any frame to cause a digest cycle in all other frames. Allowing simple ng-clicks bound directly to a factory method to update the view in all other frames. I created a module which handles the binding of scopes and sharing of factories:

Click here to see plunkr

Just include the module on each app:

angular.module('App', ['iFrameBind'])

And change any factory's return statement to return the shared version of that factory:

return sharedFactory.register('counter', service);

e.g.

.factory('counter', function (sharedFactory) {

  var service;
  var val = 0;

  function inc() {
    val++;
  }

  function dec() {
    val--;
  }

  function value() {
    return val;
  }

  service = {
    inc: inc,
    dec: dec,
    value: value
  };

  return sharedFactory.register('counter', service);
})

.directive('counter', function (counter) {
  return {
    template: '{{counter.value()}} ' +
              '<button ng-click="counter.inc()">Up</button> ' +
              '<button ng-click="counter.dec()">Down</button> ',
    restrict: 'E',
    link: function postLink(scope) {
      scope.counter = counter;
    }
  };
});

Counter updates in both frames when click happens in either

Phil Holden
  • 194
  • 8