- or -
The curious incident of the my-account evolution
Ofir Dagan
*(based on a best seller slides by Shahar Talmi)
This is not what this presentation is about!
So what did we do?
Surprisingly, we don't like iframes.
We want each widget to be a deployable app with it's own directives, services, controllers... But we also want it to be isolated
And we want it to load lazily since widgets might be removed or added during run-time in the future
We usually tell angular what module we want to manage our page with the ng-app attribute
When the page loads, angular looks for ng-app, creates a new injector (which holds all the injectables), loads that module and all its dependencies, and $compiles the DOM element
But if you load scripts asynchronously and want to wait with all this until you are ready, you leave out ng-app and instead invoke angular.bootstrap(document, ['myApp'])
Actually, you can angular.bootstrap many times, each time for a different element and module
Each run will create a different injector with different instances of all injectables, completely isolated from the rest of the apps in the page
We have a pretty simple directive which lazy loads the widget's js, css, html, etc. It then calls angular.boostrap with the widget's module name and html
widgetsProvider
.setManifestGenerator(function (name) {
return manifestLoader.loadWidgetManifest({
staticsBaseUrl: clientTopology.baseUrl,
manifestUrl: 'my-widget.manifest.json',
config: [runInConfigTime()]
});
});
<ng-widget src="'widget-name'"></ng-widget>
{
"module": "myWidgetApp",
"html": "views/main.view.preload.html",
"scripts": [
{
"debug": [
"scripts/app.js",
"scripts/constants/pages.js",
],
"release": "scripts/scripts.js"
},
"styles": [
{
"debug": [
"styles/main.css"
],
"release": "styles/main.css"
}
],
"locale": [
{
"debug": [
"scripts/locale/messages_${locale}.js"
],
"release": "scripts/locale/messages_${locale}.js"
}
]
}
downloadWidget(
manifest.html, manifest.files)
.then(function (html) {
var element = angular.element(html),
angular.bootstrap(element,
[manifest.module]),
node.html('').append(element);
});
angular.bootstrap = function (element, modules) {
if (element.injector()) throw 'already bootstrapped!';
modules.unshift(functioan ($provide) {
$provide.value('$rootElement', element);
}
var injector = createInjector(modules);
injector.invoke(function ($rootScope, $rootElement,
$compile, $injector) {
$rootScope.$apply(function () {
$rootElement.data('$injector', $injector);
$compile($rootElement)($rootScope);
});
});
return injector;
}
$routeProvider.when('/app1/:eatall*?', {
template: '<ng-widget src="\'app1\'" />'});
$routeProvider.when('/app2/:eatall*?', {
template: '<ng-widget src="\'app2\'" />'});
$routeProvider.when('/app3/:eatall*?', {
template: '<ng-widget src="\'app3\'" />'});
You set ng-route and manifest generator for "mother app"
You can do whatever you want in "micro app"
Including having an internal router! (ng-route or ui-router)
You can even have nested widgets inside your apps/widgets
Wanna see a demo?
Wanna see an impressive demo?
You can instruct widgets provider to share a service
widgetsProvider.addServiceToShare('service-name');
$location is a shared service by default
You can instruct widgets provider to propagate scope events
widgetsProvider.addEventToForward('event-name');
$locationChangeStart is a propagated scope event
Pros:
Cons:
One App to rule them all!
https://github.com/wix/angular-widget
$ bower install angular-widget
* demo code is inside
** pull requests are welcome