Programming patterns

This topic covers common usage patterns and best practices for components and the core API when building applications with the ArcGIS Maps SDK for JavaScript.

Attributes and properties

ArcGIS Maps SDK for JavaScript components' HTML attributes and JavaScript properties, while similar in concept, have some key differences. HTML attributes are always strings and they can be set directly on the HTML element or programmatically in JavaScript. Even when setting what seems like a number, the attribute value is still a string. In HTML, the presence of an boolean attribute means it is true and its absence means it is false, such as drag-enabled in the snippet below. Setting attribute strings to "true" and "false" is invalid. Attributes are written in kebab-case, while properties are in camelCase.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
<body>
  <arcgis-map id="my-map" item-id="05e015c5f0314db9a487a9b46cb37eca"></arcgis-map>
  <arcgis-layer-list
    reference-element="my-map"
    drag-enabled
    selection-mode="multiple">
  </arcgis-layer-list>
</body>

On the other hand, JavaScript properties can be any valid JavaScript data type, including strings, booleans, numbers, arrays, objects, functions, and more. Properties can only be set programmatically using JavaScript. Most properties have an associated attribute, but not all. If a property is any type besides a string, number, or boolean, it will not have an associated attribute.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Get a reference to the arcgis-layer-list component
const arcgisLayerList = document.querySelector("arcgis-layer-list");

// Set properties with various data types

// arcgis-map component
arcgisLayerList.referenceElement = document.querySelector("my-map");
// boolean
arcgisLayerList.dragEnabled = true;
// function
arcgisLayerList.listItemCreatedFunction = (event) => {
  const { item } = event;
  if (item.layer.type != "group") {
    item.panel = {
      content: "legend"
    };
  }
};
// string
arcgisLayerList.selectionMode = "multiple";
// object
arcgisLayerList.visibleElements = {
  ...arcgisLayerList.visibleElements,
  ...{
    statusIndicators: false
  }
};

Boolean attributes

Boolean attributes present a special case in HTML elements. The presence of the attribute in an HTML tag always indicates a value of true, and absence of the attribute from the tag indicates a value of false. It is invalid to set a boolean attribute using the string values "true" or "false". Here is an example using the Map components popupDisabled attribute:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
<!-- Best practice: set popupDisabled to true -->
<arcgis-map popupDisabled></arcgis-map>

<!-- Best practice: set popupDisabled to false (default) -->
<arcgis-map></arcgis-map>

<!-- Invalid: don't use strings to set boolean values -->
<arcgis-map popupDisabled="true"></arcgis-map>

More information is available in the Boolean attribute (HTML) MDN article.

HTML start and end tags

Components from the ArcGIS Maps SDK for JavaScript are standards-based custom HTML elements, which means they must have a start and end tag in HTML. They cannot be self-closing.

Use dark colors for code blocksCopy
1
2
3
4
5
<!-- Best practice: start and end tags -->
<arcgis-map></arcgis-map>

<!-- Invalid: self-closing tags -->
<arcgis-map />

Associate components with a Map or Scene

The Map and Scene components are parent containers for building interactive 2D and 3D visualization experiences. Other components from the @arcgis/map-components package can be associated with a Map or Scene component to provide additional functionality, such as adding a legend, layer list, search, and more. There are two methods to associate components from @arcgis/map-components package to a Map or Scene component. The first method involves including the component as a child element of the Map or Scene component.

Use dark colors for code blocksCopy
1
2
3
<arcgis-map item-id="05e015c5f0314db9a487a9b46cb37eca">
  <arcgis-legend position="bottom-right"></arcgis-legend>
</arcgis-map>

The second is to use the reference-element attribute on the component. By passing the id of the Map or Scene component into this attribute, you can position components from the @arcgis/map-components package anywhere in the DOM while still maintaining a connection to the Map or Scene.

Use dark colors for code blocksCopy
1
2
<arcgis-map id="my-map" item-id="05e015c5f0314db9a487a9b46cb37eca"></arcgis-map>
<arcgis-legend reference-element="my-map"></arcgis-legend>

Watch for changes in web components

Watching for changes on properties or attributes on a JavaScript Maps SDK component is essential for detecting and implementing custom responses to those changes in your application. You can watch for changes using a component's events, a mutation observer or a proxy. The Watch for changes in components sample demonstrates some of these concepts.

Using component events

Components may emit information about changes to their state as events. The changes can be initiated by internal changes, user interaction or when setting properties or attributes. Listen for events by using the addEventListener method. The event's payload contains information about the change event, such as the action that was triggered, the item that was clicked, or the new state of the component.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
const arcgisLayerList = document.querySelector("arcgis-layer-list");
arcgisLayerList.addEventListener("arcgisTriggerAction", async (event) => {
  // event.detail is used here since the event type is CustomEvent<LayerListTriggerActionEvent>
  // It returns data passed when initializing the event.
  // https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail
  const { action, item } = event.detail;
  await item.layer.load();
  if (action.id === "information") {
    // do something;
  }
});

Map interactions such as panning, zooming, clicking and dragging can be tracked using events. For example, to watch for view-related property changes, such as zoom, on the arcgis-map component, use the arcgisViewChange event.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
const map = document.querySelector("arcgis-map");
map.addEventListener("arcgisViewChange", (event) => {
  // event.target provides a reference to the object that dispatched the event
  // event.target is used here since the event type is CustomEvent<void>
  // The void means that there are no details provided by the event, itself.
  const { zoom } = event.target;
  console.log(`The zoom is ${zoom}`);
})

Additionally, the arcgisViewReadyChange event can be used to watch for when the view is ready or if the view's map or scene have been replaced

Use dark colors for code blocksCopy
1
2
3
4
const map = document.querySelector("arcgis-map");
map.addEventListener("arcgisViewReadyChange", () => {
  console.log(`The view is ready`);
})

Using a mutation observer

A mutation observer is useful for watching changes that are reflected on the DOM tree. JavaScript objects and arrays cannot be watched with a mutation observer because they are not part of the DOM, and not every attribute on a component is reflected, meaning that not every attribute will trigger a mutation observer.

The observer approach allows for efficient tracking of mutations over the entire DOM tree, without needing an event listener. Observers use weak references to DOM nodes, so when nodes are removed they can be garbage collected. And, you can also continue to observe disconnected DOM nodes.

Changes that are reflected on the DOM can be visibly observed in a browser's developer tools. One example is when a map is panned, the arcgis-map component's updating attribute will appear for a few seconds.

Before panning the map:

Use dark colors for code blocksCopy
1
<arcgis-map item-id="05e015c5f0314db9a487a9b46cb37eca"></arcgis-map>

During panning the map:

Use dark colors for code blocksCopy
1
<arcgis-map item-id="05e015c5f0314db9a487a9b46cb37eca" updating></arcgis-map>

In the example below, the mutation observer will fire a callback function when any of the arcgis-map component's attributes are modified by the application or the component.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const map = document.querySelector("arcgis-map");
const observer = new MutationObserver((mutations, observer) => {
  for (let mutation of mutations) {
    console.log(`Mutation observer: ${mutation.attributeName} changed to ${mutation.target[mutation.attributeName]}`);
  }
});

// Start observing the map's attributes for changes, such as the updating property
observer.observe(map, { attributeFilter: ["updating"] });

// Change the zoom level after 10 seconds
setTimeout(() => {
  map.zoom = 7;
}, "10000");

The References for Maps SDK components list the properties that are reflected on the DOM and can be watched with a mutation observer.

Using a proxy

A proxy is useful for observing or trapping changes your application makes programmatically to a component's properties before the component can act on them. In the example below, the proxy will fire a callback function when any of the arcgis-map component's properties are modified. The set method only applies to properties that you explicitly set in your code. When these changes are trapped, they are essentially intercepted and you can take any action you want before passing on the new value or modifying it in some way. You can also read more about Proxy at javascript.info.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  const map = document.querySelector("arcgis-map");

  // A handler for the proxy to define which operations will be intercepted
  // and how to redefine them
  const handler = {
    // obj - the object being proxied (in this case, the map)
    // prop - the property of the object that is being set
    // value - the new value of the property
    set(obj,prop,value) {
      obj[prop] = value; // set property to new value
      console.log(`Proxy: the ${prop} property was changed to ${value}`);
      return true;
    }
  }

  // Initialize the new proxy
  let proxy = new Proxy(map, handler);

  // Change to a different basemap and zoom level
  const changeBasemap = () => {
    proxy.zoom = 7;
    proxy.basemap = "streets-vector";
  }

Watching for changes in the API

reactiveUtils provides the ability to track changes in API properties with a variety of different data types and structures, such as strings, booleans, arrays, collections, and objects. The module also allows for combining properties from multiple sources. It includes the following methods: watch(), on(), once(), when(), and whenOnce().

reactiveUtils also provide TypeScript type checking. You can access properties, build objects or perform other calculations and it is all properly checked by the TypeScript compiler. Callback parameters are also correctly inferred from the getValue function.

Position custom content on a map or scene component

The arcgis-map and arcgis-scene components can be used as containers for custom content, such as buttons, or other HTML elements. To add custom content, you can use the arcgis-placement component. This component allows you to position custom content on the map or scene using the position attribute.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
<arcgis-map item-id="d5dda743788a4b0688fe48f43ae7beb9">
  <arcgis-placement position="bottom-left">
    <button>Click me</button>
    <arcgis-expand>
      <arcgis-legend></arcgis-legend>
    </arcgis-expand>
  </arcgis-placement>
</arcgis-map>

Configure global properties

You can use the global esriConfig variable to configure global properties of the JavaScript Maps SDK (API and components), such as the portalUrl.

Set the portalUrl in an AMD application

Importing esri/config is necessary to configure the global esriConfig object in AMD applications. The $arcgis global is a new promise-based way of importing modules in ArcGIS Maps SDK for JavaScript AMD projects without the need for require. The $arcgis global is available in the ArcGIS Maps SDK for JavaScript 4.28 and later for AMD projects.

AMD
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<html>
  <head>
    <!-- Load the ArcGIS Maps SDK for JavaScript -->
    <link rel="stylesheet" href="https://js.arcgis.com/~4.30.0/esri/themes/dark/main.css" />
    <script src="https://js.arcgis.com/~4.30.0/"></script>
    <script>
      require(["esri/config"], (esriConfig) => {
        esriConfig.portalUrl = "https://myHostName.esri.com/arcgis";
      });
      // Optional: $arcgis global instead of require
      // async function load() {
      // 	const esriConfig = await $arcgis.import("esri/config");
      // 	esriConfig.portalUrl = "https://myHostName.esri.com/arcgis";
      // }
      // load();
	</script>
    <!-- Load map components -->
    <script type="module" src="https://js.arcgis.com/map-components/~4.30.0/arcgis-map-components.esm.js"></script>
  </head>
  <body>
    <arcgis-map item-id="d5dda743788a4b0688fe48f43ae7beb9">
      <arcgis-legend position="bottom-left"></arcgis-legend>
    </arcgis-map>
  </body>
</html>

Set the portalUrl in an ESM application

Importing @arcgis/core/config is necessary for configuring the global esriConfig object in ESM applications. Web maps hosted on a different portal can be initialized before being passed to the arcgis-map component:

ESM
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import esriConfig from "@arcgis/core/config";
import WebMap from "@arcgis/core/WebMap";
import IdentityManager from "@arcgis/core/identity/IdentityManager";
import OAuthInfo from "@arcgis/core/identity/OAuthInfo";

const portalUrl = "https://myHostName.esri.com/arcgis";
const mapElem = document.querySelector("arcgis-map");

esriConfig.portalUrl = portalUrl;

// Bonus: register OAuth info
IdentityManager.registerOAuthInfos([
  new OAuthInfo({
    appId: "...",
    portalUrl
  });
]);

const webmap = new WebMap({
  portalItem: {
    id: "..."
  }
})

// Pass the webmap to the `arcgis-map` component
mapElem.map = webmap;

Localization

The language and direction of some web components, such as @arcgis/charts-components and @arcgis/coding-components, can be inherited from the root of the HTML document in the host application, the component itself, or the component's closest parent element. This is a bottom up approach. In cases where a language and direction are not provided, the component will default to left-to-right (ltr) and English (en). See locale support for the full list of supported languages.

Setting the language

Use the ISO language code for setting the language in the lang attribute.

Use dark colors for code blocksCopy
1
<html lang="fr"></html>

Setting the direction

You only need to do this for Arabic and Hebrew.

Use dark colors for code blocksCopy
1
<html dir="rtl" lang="he"></html>

Widget viewModel pattern

Additional functionality can be implemented using the viewModel for many of the widgets. Each widget has a view and a viewModel. The view is generally responsible for handling the User Interface (UI) of the widget, for example the Sketch widget. The viewModel is typically responsible for the business logic, for example SketchViewModel.

The viewModels extend esri/core/Accessor and take advantage of its capabilities. This helps keeps consistency between various parts of the API.

Widget state is derived from both view and viewModel properties. At some point within the widget's lifecycle, the view calls the viewModel's methods and properties. Changes to the viewModel can also trigger the view to update the UI.

Here is a code snippet using the SketchViewModel.polygonSymbol property to override the default drawing symbology:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const polygonSymbol = {
  type: "simple-fill", // autocasts as new SimpleFillSymbol()
  color: "#F2BC94",
  outline: {
    // autocasts as new SimpleLineSymbol()
    color: "#722620",
    width: 3
  }
};

const sketchViewModel = new SketchViewModel({
  view: view,
  layer: graphicsLayer,
  polygonSymbol: polygonSymbol,
});

Using ESM import statements

ES module import statements are used to load individual API modules. There are three forms of import declarations: default, namespace and named:

Use dark colors for code blocksCopy
1
2
3
4
5
// Default import
import WebMap from "@arcgis/core/WebMap.js";

// Namespace import
import * as projection from "@arcgis/core/geometry/projection.js";

At the top of each module's API Reference page is guidance on which import syntax to use: default or namespace. Most modules use a default import syntax as shown in the import WebMap Class example above. Other modules, such as those providing helper functions, use a namespace import similar to the import * as projection example. Depending on your stylistic preference, instead of a namespace import, you can also use a named import to reference the exact method that you need:

Use dark colors for code blocksCopy
1
2
// Named import
import { load, project } from "@arcgis/core/geometry/projection.js";

You can also use an alias if you think there could be a variable naming conflict; for example, several classes use the load() method:

Use dark colors for code blocksCopy
1
2
// Named import using an alias
import { load as projectionLoad, project } from "@arcgis/core/geometry/projection.js";

Whether you use default, namespace or named imports, module bundlers such as esbuild, rollup.js and Webpack will treat them the same when it comes to treeshaking, which is the process of removing unused code in order to create the smallest, most efficient build.

Autocasting

Autocasting casts JavaScript objects as ArcGIS Maps SDK for JavaScript class types without the need for these classes to be explicitly imported by the app developer.

In the following code sample, five API classes are needed to create a SimpleRenderer for a FeatureLayer.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require([
  "esri/Color",
  "esri/symbols/SimpleLineSymbol",
  "esri/symbols/SimpleMarkerSymbol",
  "esri/renderers/SimpleRenderer",
  "esri/layers/FeatureLayer",
], (
  Color, SimpleLineSymbol, SimpleMarkerSymbol, SimpleRenderer, FeatureLayer
) => {

  const layer = new FeatureLayer({
    url: "https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/WorldCities/FeatureServer/0",
    renderer: new SimpleRenderer({
      symbol: new SimpleMarkerSymbol({
        style: "diamond",
        color: new Color([255, 128, 45]),
        outline: new SimpleLineSymbol({
          style: "dash-dot",
          color: new Color([0, 0, 0])
        })
      })
    })
  });

});

With autocasting, you don't have to import renderer and symbol classes; the only module you need to import is esri/layers/FeatureLayer.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require([ "esri/layers/FeatureLayer" ], (FeatureLayer) => {

  const layer = new FeatureLayer({
    url: "https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/WorldCities/FeatureServer/0",
    renderer: {                        // autocasts as new SimpleRenderer()
      symbol: {                        // autocasts as new SimpleMarkerSymbol()
        type: "simple-marker",
        style: "diamond",
        color: [ 255, 128, 45 ],       // autocasts as new Color()
        outline: {                     // autocasts as new SimpleLineSymbol()
          style: "dash-dot",
          color: [ 0, 0, 0 ]           // autocasts as new Color()
        }
      }
    }
  });

});

To know whether a class can be autocasted, look at the ArcGIS Maps SDK for JavaScript reference for each class. If a property can be autocasted, the following image will appear:

autocast label

For example, the documentation for the property renderer of the FeatureLayer class has an autocast tag.

Notice the code using autocasting is simpler and is functionally identical to the above code snippet where all the modules are explicitly imported. The ArcGIS Maps SDK for JavaScript will take the values passed to the properties in the constructor and instantiate the typed objects internally.

Keep in mind there is no need to specify the type on properties where the module type is known, or fixed. For example, look at the outline property in the SimpleMarkerSymbol class from the snippet above. It doesn't have a type property because the only Symbol subclass with an outline property is SimpleLineSymbol.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
const diamondSymbol = {
  type: "simple-marker",
  outline: {
    type: "simple-line", // Not needed, as type `simple-line` is implied
    style: "dash-dot",
    color: [ 255, 128, 45 ]
  }
};

In cases where the type is more generic, such as FeatureLayer.renderer, then type must always be specified for autocasting to work properly.

Using the ESM CDN

The ES modules CDN is for testing only, it is not optimized for performance. For best performance in a production application, build the @arcgis/core modules locally. A sample is available in the jsapi-resources GitHub repository. Here's an HTML and JavaScript code snippet:

index.html

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
<link rel="stylesheet" href="https://js.arcgis.com/4.30/@arcgis/core/assets/esri/themes/light/main.css">

<script type="module">
  import Map from 'https://js.arcgis.com/4.30/@arcgis/core/Map.js';
  const map = new Map({
    basemap: "topo-vector"
  });
</script>

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.