Building large scale Angular applications
- or -
The curious incident of the my-account evolution
Join live presentation: http://tinyurl.com/ng-widget
Shahar Talmi
Who's This Guy?
- Shahar Talmi
- Architect & Angular geek @ Wix
- Angular collaborator
- Doctor Who fan
@shahata
https://github.com/shahata
Let's Talk About Huge Applications
By Type
-
app/
- controllers/
- services/
- directives/
By Feature
-
app/
- dashboard/
- contacts/
- activities/
- market/
This is not what this presentation is about!
What About Them?
- Hard to maintain without making spaghetti
- Multiple teams working on same project
- One big deployable => risky deployments
- One big build => tests run forever
- Everything needs to load on page load
- Inferior overall performance
- I don't like them
So what did we do?
AnCient History
- We had iframes, one per every application section
- The hosting page needed to:
- Resize/move the iframe
- Show modals (and put iframes inside the modals)
- Communicate deep links over iframe address
- No, iframes are no fun at all
The Big Rewrite
The Big Rewrite
- Angular here we come!
- Okay, this time without iframes
- Except for third-party apps, for security reasons
- But no huge application, we are not ready for that yet
- So we created many single page applications
- Avoid single page app that contains multiple apps
- BUT: Full page refresh when navigating between apps
- Deal with it later!
Text
This is slow motion, our application is five times faster :P
Many apps !== duplicate code
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 --->
Many apps !== duplicate BUILD
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
But Something Else Happened In this Rewrite
Widgets
Surprisingly, we don't like iframes.
HOW WE SEE WIDGETS
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
A SHORT ANGULAR STORY
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'])
Title Text
MIND BLOWN
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>
SIMPLIFIED CODE
SIMPLIFIED CODE
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;
}
So what does bootstrap do Again?
Not surprising But...
Applications are actually fullScreen widgets
$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\'" />'});
All you need is to connect the widget to a router
Wait, What?
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?
Sharing Is Caring
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
Be Careful
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
Conclusion
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
Use It
https://github.com/wix/angular-widget
$ bower install angular-widget
Thanks!
* demo code is inside
** pull requests are welcome
angular-widget
By Shahar Talmi
angular-widget
- 10,424