Building large scale Angular applications

- or -

The curious incident of the my-account evolution

 

 

Ofir Dagan

*(based on a best seller slides by Shahar Talmi)

Who's This Guy?

  • Ofir Dagan
  • My Account TL (soon to be crm mobile TL)
  • Doggie Sitter @ Wix

 

 

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

  • Okay, this time without iframes
  • Except for third-party apps, for security reasons
  • 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!

Widgets

Surprisingly, we don't like iframes.

HOW WE SEE WIDGETS

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 manifestLoader.loadWidgetManifest({
  staticsBaseUrl: clientTopology.baseUrl,
  manifestUrl: 'my-widget.manifest.json',
  config: [runInConfigTime()]
 });
});

<ng-widget src="'widget-name'"></ng-widget>

SIMPLIFIED CODE

{
  "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"
    }
  ]
}

Manifest.json

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"

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

Bottom line - Pros & cons

Pros:

  • No Iframes :)
  • Sharing services across injecotrs
  • Minimizing bandwidth
  • just plain cool
  • css are shared (we use namespaces)
  • commons libraries are shared
  • Upgrade common library effects everyone
  • widgets share global namespace

Cons:

My Account vision

One App to rule them all!

Check it out

https://github.com/wix/angular-widget

$ bower install angular-widget

Thanks!

* demo code is inside

** pull requests are welcome

angular-widget

By ofird

angular-widget

  • 1,525