Angular Directives in Modules

A primary use case of having multiple goog.provide statements per file is with Angular directives and their controller. OpenSphere prefers to pair the directive and controller within the same file given they are coupled to create the UI. This poses a problem for a backward-compatible transition to modules, given only one module name is allowed per file.

Our options were to split the original names into separate files, or change our approach to exposing the UI. We decided to use a shim to maintain compatibility with existing code. This seems to result in the best end product, and will be detailed in this guide.

The following example shows how the map directive was transitioned to a module.

Note

For a full example, see the map directive source here.

Creating the UI Module

  • The goog.module value can use the original controller name, minus Ctrl. The name can be adjusted if needed, for example if this convention results in a name conflict with another class. If unsure about naming conflicts, we recommend replacing Ctrl with UI in the module name.
  • Define and export the controller class as Controller, and the directive function as directive. This will ensure consistency across all UI’s.
src/os/ui/map.js
goog.module('os.ui.MapUI');
goog.module.declareLegacyNamespace();

/**
 * Controller for the map directive.
 */
class Controller {
  // Controller class body
}

/**
 * The map directive.
 * @return {angular.Directive}
 */
const directive = () => ({
  // Directive definition
});

// Export on the module as Controller and directive.
exports = {Controller, directive};

Using the Module

To reference the UI in goog.module files:

const MapUI = goog.require('os.ui.MapUI');
// reference the controller class as MapUI.Controller
// reference the directive function as MapUI.directive

Note

This intentionally uses the name convention <class>UI both for clarity that it’s a UI where referenced, and the avoid shadowing the native Map object when assigned to a variable of the same name.

To reference the UI in legacy goog.provide files:

goog.require('os.ui.MapUI');
// reference the controller class as os.ui.MapUI.Controller
// reference the directive function as os.ui.MapUI.directive

Backward Compatibility Shim

When converting existing UI’s to modules, we would like to avoid breaking changes where possible. To avoid a breaking change with a converted module, we’ll create a shim to provide the old namespaces. The shim needs to accomplish a couple things:

  • Make the old namespaces available to goog.require statements.
  • Deprecate the old namespaces so developers are aware of the change.
src/os/ui/map_shim.js
goog.provide('os.ui.MapCtrl');
goog.provide('os.ui.mapDirective');

goog.require('os.ui.Map');

/**
 * @deprecated Please use goog.require('os.ui.Map').Controller instead.
 */
os.ui.MapCtrl = os.ui.Map.Controller;

/**
 * @deprecated Please use goog.require('os.ui.Map').directive instead.
 */
os.ui.mapDirective = os.ui.Map.directive;

The shim will be maintained until all code referencing the old namespaces has been transitioned to directly reference the new module. After a suitable (TBD) amount of time has passed for developers to update their code, the shim will be deleted.

Note

New UI’s do not need a shim because all references to them can be guaranteed to use the new format.

Warning

The deprecation warning will not appear as a result of goog.require. The Closure compiler will only issue a warning if the old directive/controller references are invoked in code.