About components

A bundle can declare components to provide instances of JavaScript objects that are created and managed by the App Runtime as needed. Other component instances — declared by the same or a different bundle — can discover and use ("consume") them.

An example

The "map-init" bundle declares a component that provides an instance of a map frame. The "scalebar" bundle declares a component that requires a map frame instance, so it can read the scale of the frame and display the scale in the bottom right corner of a map.apps app.

The App Runtime creates the components and provides the the requested map frame instance to the scalebar instance as soon as it becomes available.

In the example above the map frame component provides a service (a map frame instance) while the scalebar component requires a service (a map frame instance). The important thing is that the components have no direct dependencies: the scalebar is satisfied as soon as it gets a map frame, no matter which bundle provides the service.

Component declaration

To declare a component in a bundle, add a component description to the "components" array of the bundle manifest like this:

Component description in manifest.json file
{
  "name" : "zoom",
  "version": "1.0.0",
  "components" : [
    {
      "name": "ZoomSlider",        (1)
      "impl": "ZoomSliderImpl",    (2)
      "provides": "zoom.Control",  (3)
      "properties": {              (4)
        "+fooProperty": "foo",
        "barProperty": "bar"
      }
    }
  ]
}
1 Name of this component. This is the only required property. The name must be unique within this bundle.
2 Name of the implementation class of the component.
3 Identifier for the service provided to other components.
4 List of public and private component properties.

Visit the component configuration reference for a list of all supported properties of a component description.

Implementation class

All components must have a corresponding JavaScript class that implements the component’s behavior. By default the name of the component is used to locate its implementation class.

As an example, if the component "name" is "ZoomSlider", the App Runtime tries to resolve the class name ZoomSlider by looking into the object returned by the module.js file.

If the implementing class has a different class name, add the property "impl" to specify the actual class name like this:

{
  "name" : "ZoomSlider",
  "impl" : "ZoomSliderImpl"
}

Find more details about the class loading mechanism in Bundle layer file.

Providing a service

A component instance is registered as a service in the App Runtime if its component description declares the property provides. The property contains the names of the provided service interfaces, the value can be a string or an array.

{
  "name" : "ZoomSlider",
  "provides" : "zoom.Control"
},
{
  "name" : "ZoomInButton",
  "provides" : ["zoom.Control","zoom.Button"]
}

The properties specified in the components description are also important for service registration, see Component Properties. It is important that each property which does not start with a _ prefix is treated as public service property and registered as service properties.

Instance factories

Sometimes it is required to register a different instance as a service than the one that you want to configure as a component implementation. This is often the case if you have a ready-to-use class which is implemented independent of the component model and you do not want to introduce component system-relevant code inside this class, for example configuration code. A very typical use case is the use of Dijit widgets as components. To support this case the component description has to be marked as "instanceFactory", which means that the component instance acts as factory for the service.

{
  "name" : "ExternalZoomSliderFactory",
  "provides" : ["zoom.Control"],
  "instanceFactory": true
}

A component instance which is marked as an instance factory must implement the method createInstance, which is responsible for creating the "real" service instance. This service instance is registered at the App Runtime. Optionally the component instance can provide a destroyInstance method, which is responsible for cleaning up the service instance during shutdown.

ExternalZoomSliderFactory.js
import ZoomSlider from "./zoomToolsLib";

export default class {
    createInstance() {
        return new ZoomSlider();
    },
    destroyInstance(instance) {
        instance.destroyRecursive();
    }
}

Component properties

Properties declaration

A component description can declare properties. These properties are used for two purposes. The first is to provide a configuration mechanism and the second to specify service properties (if the "provides" property is defined).

Components: properties
{
  "name" : "ZoomSlider",
  "provides" : ["myservice.Interface"],
  // properties of the component
  "properties": {
    // underscore properties are private properties
    "_offset" : 125,
    // all other properties are registered as service properties (if provides exist)
    "url" : "http://locahost:8080/..."
  }
}

The recommended way to access component properties within an implementation class is to use the provided property _properties. This is a plain JavaScript object which can be used to access the configuration properties.

Components: injected properties
export default class {
    aServiceMethod(){
      // accessing the injected '_properties' object
      let properties = this._properties;
      // the url property
      let url = properties.url;
      // the offset property
      let offset = properties._offset;
      ...
    }
}

An alternative way is using the Component Context provided by the activate method (see component lifecycle methods). The Component Context provides a getProperties method, which returns a ct.Hash instance containing the properties, which means that the access style is slightly different.

Components: programmatic properties
export default class {
    activate(componentContext){
      // get the properties from ComponentContext
      var properties = componentContext.getProperties();
      // the url property
      var url = properties.get("url");

      // the offset property
      var offset = properties.get("_offset");
      ...
    }
}

Another way to retrieve the properties is to specify the flag "propertiesConstructor" on the component description. This defines that the configuration properties are transported as constructor options into the implementation class. Here the properties are exactly like the '_properties' property a plain JavaScript object. Besides the transportation as constructor properties, the configuration properties are still injected as _properties property.

Components: propertiesConstructor
{
  "name" : "ZoomSlider",
  "provides" : ["myservice.Interface"],

  // the component implementation shall get the properties as constructor options
  "propertiesConstructor": true,
  "properties": {
    "_offset" : 125,
    "url" : "http://locahost:8080/..."
  }
}
Components: properties constructor access
export default class {
    constructor(properties){
      // the url property
      let url = properties.url;

      // the offset property
      let offset = properties._offset;
      ...
    }
  });
});

Configuration properties are read only. You are not allowed to change them.

Properties visibility

Properties can be public or private. Public properties are part of the component interface and registered as service properties if a component instance is registered as a service.

By default, all properties prefixed with an underscore _ are treated as private properties, while all other properties are treated as public.

You can override the default by prefixing property names with a + to make them public, and - to make them private. If you use the + prefix for at least one property, all other properties are private. Using - as a prefix to make a property private has no effect on the visibility of other properties.

{
    "name": "ZoomSlider",
    "provides" : "anInterface",
    "properties": {
        "+enableFoo": true,
        "-internalProp" : "prop"
    }
}

Accessing internationalization properties

A component instance can access the internationalization (i18n) properties provided by the bundle using the _i18n property, injected into it. The _i18n property is a ct.I18N object. The _i18 property is also provided in the constructor if the component configuration property "propertiesConstructor": true is set.

Access i18n key in a component
export default class {
    aServiceMethod() {
      // get a i18n value of key 'i18nProperty'
      let i18nValue = this._i18n.get().i18nProperty;
      ...
    }
}