<!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>