Access to global variables in Angular 2

Don’t use window directly.

Angular 2 has an ability to develop an application in cross-platform because it doesn’t depend on DOM. But it’s breakable easily. If you use window,document or anything browser-specific, then of course your app will lose the ability.

We often use window instance to get and set global variables. In browser platform, window is a single global context object. In the other side, Node.js environment provides a global context as global. To make our app platform-agnostic, we must absorb the difference. Don’t worry. Already we have a powerful stuff for that.

Dependency injection.

Define a global variable

Prepare a global variable foo as example.

<body>
  <my-app>
    loading...
  </my-app>
  <script>
    window.DATA = {
      foo: 'bar'
    };
  </script>
</body>

Declare global type

First, we should declare our global type as an interface. In this case, global type has only foo string field.

export interface MyGlobal {
  foo: string;
}

Create GlobalRef class

This is a hero of this story. GlobalRef is an abstract class to access to the global object. It has only one nativeGlobal getter. (consistent with ElementRef#nativeElement.)

export abstract class GlobalRef { 
  abstract get nativeGlobal(): MyGlobal;
}

Create platform-specific classes

GlobalRef class is just a placeholder. Let’s make platform-specific classes by extending GlobalRef.

export class BrowserGlobalRef extends GlobalRef {
  get nativeGlobal(): MyGlobal { return window as MyGlobal; }
}
export class NodeGlobalRef extends GlobalRef {
  get nativeGlobal(): MyGlobal { return global as MyGlobal; }
}

Provide GlobalRef

We must provide the classes. Let’ create SharedModule and define SharedModule.forBrowser() and SharedModule.forNode().

import { NgModule, ModuleWithProviders } from '@angular/core';
import { GlobalRef, BrowserGlobalRef, NodeGlobalRef } from './global-ref';
@NgModule({})
export class SharedModule {
  
  static forBrowser(): ModuleWithProviders {
    return {
      ngModule: SharedModule,
      providers: [
        { provide: GlobalRef, useClass: BrowserGlobalRef }
      ]
    };
  }
  
  static forNode(): ModuleWithProviders {
    return {
      ngModule: SharedModule,
      providers: [
        { provide: GlobalRef, useClass: NodeGlobalRef }
      ]
    };
  }
}

And use them in BrowserAppModule and NodeAppModule.

@NgModule({
  imports: [ BrowserModule, SharedModule.forBrowser() ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class BrowserAppModule {
}
@NgModule({
  imports: [ BrowserModule, SharedModule.forNode() ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class NodeAppModule {
}

Bootstrap

Bootstrapping must be separated for each platform. Following is for only browser.

import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppBrowserModule} from './app';
platformBrowserDynamic().bootstrapModule(AppBrowserModule)
Use `GlobalRef` in components
All done! Let’s use `GlobalRef` and access to global variables.
import {GlobalRef} from './global-ref';
@Component({
  selector: 'my-app',
  template: `
    <div>
      <pre>{{ data | json }}</pre>
    </div>
  `,
})
export class App {
  data: any;
  constructor(_global: GlobalRef) {
    this.data = _global.nativeGlobal.DATA;
  }
}

Plunker: http://plnkr.co/edit/ceuEBlVpWhNNYZvMOqbO?p=preview

Conclusion

  • Don’t access to window as global context
  • Wrap the context and absorb the difference among platforms
  • Use dependency injection and NgModule

Note: I never recommend you to use global variables. This is a small tip to tackle the real world… Good luck!


comments powered by Disqus