<!DOCTYPE html>
<html>

  <head>
    <script data-require="angular.js@1.2.22" data-semver="1.2.22" src="https://code.angularjs.org/1.2.22/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    
  </head>

  <body id="parent">
    <h1>Binding factories between iFrames </h1>
    <counter></counter><hr>
    <iframe src="index2.html"></iframe>
    <iframe src="index2.html"></iframe>
    <iframe src="index2.html"></iframe>
    <script src="script.js"></script>
  </body>

</html>
// Code goes here

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

  .config(function(rootWindowProvider){
    //need to be able to identify the parent frame
    rootWindowProvider.setGetRootWindow(function(){
      return window.document.body.id === 'parent' ?
        window:
        window.parent;
    });
  })
  
  // seporate model into a factory and share it
  // Tip: it is normally best to remove as much logic as possible from
  // directives and controllers this makes the logic testabale and reusable
  .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 is almost logicless. Methods on model are call directly from
  // attributes on elements
  .directive('counter', function (counter) {
    return {
      template: '{{counter.value()}} ' +
                '<button ng-click="counter.dec()">Down</button> ' +
                '<button ng-click="counter.inc()">Up</button> ',
      restrict: 'E',
      link: function postLink(scope) {
        scope.counter = counter;
      }
    };
  });
  
  
  
// frame binding module   
angular.module('iFrameBind', [])

  
  .config(function ($provide) {
    // we provide an alternative implementation of $apply()
    $provide.decorator('$rootScope', function ($delegate) {
      var proto = Object.getPrototypeOf($delegate);
      var apply = proto.$apply;
      var postApply = [];

      proto.onPostApply = function(func) {
        postApply.push(func);
      };

      proto.$apply = function() {
        var args = Array.prototype.slice.call(arguments, 0);
        // it calls normal $apply()
        apply.apply($delegate, args);
        // then any post apply callbacks we have added
        postApply.forEach(function(func) {
          func.call();
        });
      };

      return $delegate;
    });
  })

  // provide a way to configure how we decide which is the root frame
  .provider('rootWindow', function() {
    var getRootWindow = function() {
      return window.parent === window ?
        window:
        window.parent;
    };

    function RootWindow() {
      this.get = getRootWindow;
    }

    this.setGetRootWindow = function(callback) {
      getRootWindow = callback;
    };

    this.$get = function() {
      return new RootWindow();
    };

  })

  // create a store on the root frame for our shared factories 
  .factory('sharedFactory', function (rootWindow) {
    var win = rootWindow.get();

    if (!win.sharedFactories) {
      win.sharedFactories = {};
    }

    // if factory already created by other frame it is returned
    // else register it and return it
    function register(key, val) {
      if (win.sharedFactories[key] === undefined) {
        win.sharedFactories[key] = val;
      }
      return win.sharedFactories[key];
    }

    return {
      register: register
    };

  })
  
  
  // add a postApply method to each frame that causes digest cycle on
  // all other frames
  .run(function($rootScope, $rootElement, rootWindow){
    var win = rootWindow.get();

    if (!win.rootScopes) {
      win.rootScopes = [];
    }

    win.rootScopes.push($rootScope);
    
    //remove rootscope if iframe src changes
    window.addEventListener('unload', function(){
      win.rootScopes.splice(win.rootScopes.indexOf($rootScope), 1);
    });
    
    $rootScope.onPostApply(function(){
      win.rootScopes.forEach(function(rootScope){
        if (rootScope !== $rootScope) {
          // only cause a digest cycle if one is not in progess
          rootScope.$evalAsync();
        }
      });
    });
  });
  
  angular.bootstrap(document.body, ['App']);
  
/* Styles go here */

* share factories between iFrames
* $apply() on one frame causes digest cycle on all other frames
* frames update sync rather than async making testing easier (+ faster)
* normal directives e.g. `ng-change`, `ng-click` will update all views on all frames 
* other frames digest cycle run after inital frame's so values will have settled
 
<!DOCTYPE html>
<html>

  <head>
    <script data-require="angular.js@1.2.22" data-semver="1.2.22" src="https://code.angularjs.org/1.2.22/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    
  </head>

  <body>
    <counter></counter>
    <script src="script.js"></script>
  </body>

</html>