- or -
The curious incident of the my-account evolution
Join live presentation: http://tinyurl.com/ng-widget
Shahar Talmi
@shahata
https://github.com/shahata
This is not what this presentation is about!
So what did we do?
Text
This is slow motion, our application is five times faster :P
Everything that is common we put in bower components
We run an internal bower server at Wix, but also publish stuff on public bower
Common dialogs, menus, utilities, sass, everything really, goes to bower
Yes, this means even more projects & builds to maintain, but we don't mind cuz --->
All of our projects look exactly the same thanks to YO
We have our own generator (yo wix-angular) which is based on the angular generator + some Wix goodness
Since we don't want to maintain (50+!) grunt files, we have a wix-gruntfile node module which everyone uses
The grunt file in each project only require('wix-gruntfile') and add its own custom stuff
Some of it is pretty Wix specific, but there are some interesting concepts, so take a look: https://github.com/wix/wix-gruntfile
Surprisingly, we don't like iframes.
We do like Angular. A lot. So we want widgets to be angular components.
But what is an angular component? Is it a directive? Having only a directive for each widget seems too limiting
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 {
module: camelCase(name),
html: url + 'views/' + name + '.html',
files: [
url + 'scripts/' + name + '.js',
url + 'styles/' + name + '.css'
]
};
});
<ng-widget src="'widget-name'"></ng-widget>
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"
angular.element($0).injector().get('serviceName');
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
Widgets naturally share all css rules, so css must be namespaced
Widgets share the global javascript namespace. Even two angular modules with the same name will override each other.
Common libraries should be included by hosting application and not by widgets to prevent conflicts and redundant downloads.
Upgrade of common libraries effects all widgets and must be tested thoroughly
With this architecture we have a different git repo, build and deployable for each "micro app"
Each "micro app" can be developed independently and has an index.html which is used only for development and tests
Having no monolith app that contains all parts of the app has worked great for us, but there are obviously many other approaches
There are many upcoming technologies that will assist this approach like Web components, Scoped CSS, Angular 2.0 etc. but this is the best we could do with today's tools
https://github.com/wix/angular-widget
$ bower install angular-widget
* demo code is inside
** pull requests are welcome