0

Two angular apps are on a same page, first one have a service that modifies data model. Calling the service from its parent app modifies data and DOM accordingly. But calling same service from another app makes a copy of data and makes changes on that copy only. How could angular service be called from another app so it have access to data model from it's parent app?

Here's a plunk

<div ng-app="app1">
  <div ng-controller="myctrl">
    <input type="text" value="{{data.list.name}}">
    <br>
    <button ng-click="update()">run from app1</button>
  </div>
</div>
<div id='app2div' ng-app='app2' ng-controller="ctrl2">
    <button ng-click="update2()">run from app2</button>
</div>

<script>
    var test = angular.module("app1",[]);
    test.controller("myctrl", function($scope, service)
    {
        $scope.scope1 = true;
        $scope.data=service.data;


    $scope.update=function()
    {
        service.updateValue();
    };
    }).factory("service", function()
    {
    return new (function(){
        this.data={list:{name:"name0"}},
        this.updateValue=function()
        {
        var self=this;
        self.data.list=self.values[self.count];
        self.count++;


        },
        this.values= [{name:"name1"}, {name:"name2"}, {name:"name3"}],
        this.count=0
    })();
    });

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

    app2.controller('ctrl2', function($scope, service){
        $scope.scope2 = true;
        $scope.update2 = function()
        {
            service.updateValue();
        };
    });

    angular.bootstrap(document.getElementById("app2div"), ['app2']);
</script>
11
  • what is the error that you are getting? Commented Nov 15, 2016 at 13:30
  • First of all: please don't use reserved words like new or names used by the framework, for example service. It is a very bad practice and can lead to nasty debugging problems. Commented Nov 15, 2016 at 13:31
  • The service is called in app2. There is no error there. The issue should be with the controller scope. Add this line: $scope.data = service.data; in ctrl2 after $scope.scope2 and before $scope.update2. In your HTML add this line <p>{{data.list.name}}</p> after the button "run from app2". Then you can see that the controller works fine and the service is called. Commented Nov 15, 2016 at 13:44
  • The best solution in my opinion would be to outsource the service in a third module and make it accessible to app1 and app2. So you can share the scope between the controllers. Commented Nov 15, 2016 at 14:06
  • 1
    It's no problem - You're welcome :) Commented Dec 22, 2016 at 8:52

1 Answer 1

1

Explanation

There is not really any built in support for cross-app communication in AngularJS, so it can be a bit tricky. But it is possible.

First it's important to understand the difference between module, app and instances.

In this case we will use the word app to describe the entire composition of modules.

In your case you have:

  1. app1 - which consists of the module app1.
  2. app2 - which consists of the modules app2 and app1.

Now when you use either ng-app or angular.bootstrap to bootstrap an application a new instance of the app is created.

Each instance gets its own instance of the $injector service.

Now comes an important part - each service in AngularJS is a singleton in the sense that it is only created once per $injector instance (including $rootScope).

This means that after you have bootstrapped the both applications you will have:

  1. Instance of app1 -> Instance of $injector -> Instance of service

  2. Instance of app2 -> Instance of $injector -> Instance of service

So there are two instances of service, each with their own internal state. Which is why the data is not shared in your case.

So it wouldn't even matter if you instead bootstrapped app1 two times, there would still be two instances of everything.


How to

To retrieve an app´s injector instance you can use the injector method that is available on angular elements.

Note that this is not the same as the method angular.injector, which is used to create entire new instances of $injector.

Retrieve a reference to a DOM element of a specific app that was used with ng-app or angular.bootstrap:

var domElement = document.getElementById('app1');

Turn it into an angular (jqLite) element:

var angularElement = angular.element(domElement);

Retrieve the app's injector:

var injector = angularElement.injector();

Use the injector to retrieve the correct instance of a service:

var myService = injector.get('myService')

Call the service:

myService.doSomething();

If this is done outside of Angular's digest loop you will need to trigger it manually:

var rootScope = injector.get('$rootScope');
rootScope.$apply();

Or:

var scope = angularElement.scope();
scope.$apply();

Example solution

We have two apps:

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

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

app1.controller('MyController', function($scope, sharedFactory) {

  $scope.sharedFactory = sharedFactory;
});

app2.controller('MyController', function($scope, sharedFactory) {

  $scope.sharedFactory = sharedFactory;
})

Both apps use the shared module consisting of a factory named sharedFactory with the following api:

var api = {
  value: 0,
  increment: increment,
  incrementSync: incrementSync
};

Remember that each app has its own instance of the factory.

The increment method will simply increment the value. If called from app1 the incrementation will take place in app1's instance of the service.

The incrementSync method will increment the value in both app1's and app2's service instance:

function incrementSync() {

  var app1InjectorInstance = angular.element(document.getElementById('app1')).injector();
  var app2InjectorInstance = angular.element(document.getElementById('app2')).injector();

  console.log(app1InjectorInstance === app2InjectorInstance); // false

  var app1SharedFactoryInstance = app1InjectorInstance.get('sharedFactory');
  var app2SharedFactoryInstance = app2InjectorInstance.get('sharedFactory');

  console.log('app1-sharedFactory:', app1SharedFactoryInstance);
  console.log('app2-sharedFactory:', app2SharedFactoryInstance);

  app1SharedFactoryInstance.increment();
  app2SharedFactoryInstance.increment();

  var otherInjectorInstance = app1SharedFactoryInstance === api 
    ? app2InjectorInstance 
    : app1InjectorInstance;

  otherInjectorInstance.get('$rootScope').$apply();
}

Note that if incrementSync is for example called from within app1, we will be in app1's digest loop, but we will have to start app2´s digest loop manually. This is what the part involving otherInjectorInstance does.

Demo: http://plnkr.co/edit/q2ZEG8VqxHORRzLy8wqZ?p=preview

Hopefully this will help you get going and find a solution to your problem.

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

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.