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

http://slides.com/shahartalmi/angular-widget

 

@shahata

Shahar Talmi

https://github.com/shahata

Thanks!

* demo code is inside

** pull requests are welcome

angular-widget

By Shahar Talmi

angular-widget

  • 10,228