Providing external data when bootstrapping Angular 2

Important note! - With the release of the 2.0.0-rc5 version of Angular 2 there is the new concept of application modules, and this effectively breaks the technique I show in the article below. I will work to update this article to show a technique that does work (simply using the window object to set data you can access in your application module), but wanted to share this in the interim.

Now back to the original post...

In this post I wanted to demonstrate how it's possible to provide external data to Angular 2 when bootstrapping the application. This can be useful in any situation where some data is known before Angular loads (user tokens, back-end API configuration, server-rendered variables, etc), and you want that information available when Angular is bootstrapping.

In this post I will build upon the SystemJS framework the Angular team is using, but the technique here is agnostic to any particular module loader used (I use webpack in the production apps I work on for example, and the same technique works there as well).

All of the code for this example is available in my GitHub repository.

To set the stage we'll build a simple example with the following criteria:

  1. An API configuration object
  2. A simple "api service" (that really doesn't do anything here)
  3. A single app component that requires the service
  4. An updated main.ts that sets the ApiConfig values before Angular bootstraps
  5. Using SystemJS to load it all

Let's get started!

The API configuration and service

This is pretty standard Angular 2 code, but let's show what the service looks like since we're injecting our API configuration using Angular's value provider. To keep things simple I'm also exporting the ApiConfig class from this module:

import {Injectable, Inject} from "angular2/core";

export class ApiConfig {  
    apiUrl: string;
    apiToken: string;
}

@Injectable()
export class ApiService {  
    // We can easily inject the API config using the DI value created when
    //  the application was bootstrapped
    //
    constructor(
        @Inject("api.config") private apiConfig: ApiConfig
    ) {
        console.log("Injected config:", this.apiConfig);
    }
}

Nothing too crazy there, but we can see that the console should show what API configuration was provided when this service is created. So let's move on to the main application component:

The application component

Again, this is standard Angular 2 stuff, but we set the ApiService as one of the providers for the top-level component, and inject it into the constructor so it'll be created by Angular at run time:

import {Component} from 'angular2/core';

import {ApiService} from "./api.service";

@Component({
    selector: 'my-app',
    template: `
        <h1>Angular is now running</h1>
        <div>Please check the console log to see the externally provided API config</div>
    `,
    providers: [ApiService]
})
export class AppComponent {  
    // Just inject the ApiService so the class is instantiated
    //
    constructor(private apiService: ApiService) {}
}
The new main.ts file

Here's where we start to see a change from most of the examples I've come across. In main.ts we don't just run Angular's bootstrap function, but instead wrap it in a function that needs to be called by something. The trick is that we can provide arguments to this function that let us change how Angular behaves. In our case we are passing in the API's url and a security token that would be used in a real application.

You could have Angular handle all the auth details of course, but in some of my applications I prefer to have it handled by something higher in the stack (OWIN for example). Another example is when I host this within IIS (or Azure) some of the properties are configured at the web server level.

So let's take a look at how this works:

import {bootstrap} from 'angular2/platform/browser';  
import {provide} from "angular2/core";

import {AppComponent} from './app.component';  
import {ApiConfig} from "./api.service";

// Here is the overall technique. Instead of just calling Angular's
//  bootstrap method directly, we'll export a function that accepts
//  arguments. This provides a way to "inject" external data into
//  the process, and change how Angular is bootstrapped based on
//  the provided input.
//
export function RunApplication(apiUrl: string, apiToken: string) {

    // Create our API config provider using the external data
    //
    let apiConfig = new ApiConfig();
    apiConfig.apiUrl = apiUrl;
    apiConfig.apiToken = apiToken;

    // Now we can call bootstrap, but we have the API config object
    //  set up as well. Just create is as an injectable token here
    //  so other components/services can consume it.
    //
    bootstrap(AppComponent, [
        provide("api.config", {useValue: apiConfig})
    ]);  
}

So you can see here we're exporting a function called RunApplication that needs to be called for the bootstrap process to actually run. But with this option you can provide any data you want and give yourself a lot of options for changing what Angular will do in any given situation.

The final piece of this is updating SystemJS to use our new module how we like.

Updating SystemJS to call our function

This is pretty simple as well, and we just need to adapt the Angular "getting started" example slightly to provide a callback when importing the main module:

<!DOCTYPE html>  
<html>  
  <head>
    <title>Angular 2 QuickStart</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">    
    <link rel="stylesheet" href="styles.css">

    <!-- 1. Load libraries -->
    <!-- IE required polyfills, in this exact order -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.0/es6-shim.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.26/system-polyfills.js"></script>
  <script src="https://npmcdn.com/angular2@2.0.0-beta.15/es6/dev/src/testing/shims_for_IE.js"></script>

  <script src="https://code.angularjs.org/2.0.0-beta.15/angular2-polyfills.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.26/system.js"></script>
  <script src="https://npmcdn.com/typescript@1.8.10/lib/typescript.js"></script>
  <script src="https://code.angularjs.org/2.0.0-beta.15/Rx.js"></script>
  <script src="https://code.angularjs.org/2.0.0-beta.15/angular2.dev.js"></script>

    <!-- 2. Configure SystemJS -->
    <script>
      System.config({
        transpiler: 'typescript', 
        typescriptOptions: { emitDecoratorMetadata: true }, 
        packages: {'app': {defaultExtension: 'ts'}} 
      });

      // Now we just import the main module like normal, but we're going to leverage
      //  the callback option to do something with it
      //
      System.import('app/main').then(
          // Now we're going provide a callback to let us control what happens with
          //  the imported module since all it does is export a function.
          //
          (m) => {
              // In an actual app these are simply rendered on the page using
              //  some server-side variables. The API URL could change based
              //  on where the server is deployed, and the token could be
              //  provided by some OAuth mechanism completely outside of 
              //  Angular 2.
              //
              var apiUrl = "https://my-api";
              var apiToken = "SOME_SERVER_PROVIDED_TOKEN";

              // Now to *actually* bootstrap the application we need to 
              //  call the function we created in the main module that 
              //  wraps Angular's bootstrap function. Of course, that 
              //  function is expecting the two arguments we have designed
              //
              m.RunApplication(apiUrl, apiToken); 

            }, console.error.bind(console));
    </script>
  </head>

  <!-- 3. Display the application -->
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>  

In case it's not clear, at this point it doesn't matter if it's SystemJS or some other mechanism...you just need to have some javascript call your RunApplication function to launch your app. In many of my cases the page that provides this "loading" html code is rendered on the server, so I can tweak what values are used server-side and control what Angular does on the client.

When we run all of this you should see output like the following:

Wrapping up

In this tutorial I demonstrated a relatively simple technique I've used to let me pass external data to Angular before it bootstraps. This let's me change how it behaves at a really low level and is an easy way to expand what Angular can do for you.

As always, the code for this article is available on GitHub.

Please let me know if you can improve on this, or if you have any other feedback, in the comments.

comments powered by Disqus