import { watch, when, on, once, whenOnce } from "@arcgis/core/core/reactiveUtils.js";const { watch, when, on, once, whenOnce } = await $arcgis.import("@arcgis/core/core/reactiveUtils.js");- Since
- ArcGIS Maps SDK for JavaScript 4.23
- Overview
- Using reactiveUtils
- Working with collections
- Working with objects
- WatchHandles and Promises
- Working with truthy values
Overview
reactiveUtils provide capabilities for observing changes to the state of the SDK's properties,
and is an important part of managing your application's life-cycle.
State can be observed on a variety of different data types and structures
including strings, numbers, arrays, booleans, collections, and objects.
Using reactiveUtils
reactiveUtils provides five methods that offer different patterns and capabilities for observing state:
on(), once(), watch(), when() and whenOnce().
The following is a basic example using watch(). It demonstrates how to track the
Map component updating property and then send a message to the console
when the property changes. This snippet uses a getValue function as an expression that evaluates the
updating property, and when a change is observed the new value is passed to the callback:
// Basic example of watching for changes on a boolean propertyconst viewElement = document.querySelector("arcgis-map");reactiveUtils.watch( // getValue function () => viewElement.updating, // callback (updating) => { console.log(updating) });
Working with collections
reactiveUtils can be used to observe changes within a collection, such as Map.allLayers. Out-of-the-box JavaScript methods
such as .map() and .filter() can be used as
expressions to be evaluated in the getValue function.
// Watching for changes within a collection// whenever a new layer is added to the mapconst viewElement = document.querySelector("arcgis-map");reactiveUtils.watch( () => viewElement.map.allLayers.map( layer => layer.id), (ids) => { console.log(`FeatureLayer IDs ${ids}`); });
Working with objects
With reactiveUtils you can track named object properties through dot notation (e.g. viewElement.updating) or
through bracket notation (e.g. viewElement["updating"]). You can also use the
optional chaining operator (?.). This operator
simplifies the process of verifying that properties used in the getValue function
are not undefined or null.
// Watch for changes in an object using optional chaining// whenever the map's extent changesconst viewElement = document.querySelector("arcgis-map");reactiveUtils.watch( () => viewElement?.extent?.xmin, (xmin) => { console.log(`Extent change xmin = ${xmin}`) });
WatchHandles and Promises
The watch(), on() and when() methods return a ResourceHandle. Be sure to remove watch handles when they are no longer needed to avoid memory leaks.
// Use a WatchHandle to stop watchingconst viewElement = document.querySelector("arcgis-map");const handle = reactiveUtils.watch( () => viewElement?.extent?.xmin, (xmin) => { console.log(`Extent change xmin = ${xmin}`) });
// In another functionhandle.remove()The once() and whenOnce() methods return a Promise instead of a WatchHandle.
In some advanced use cases where an API action may take additional time, these
methods also offer the option to cancel the async callback via an
AbortSignal.
Be aware that if the returned Promise is not resolved, it can also result in a memory leak.
// Use an AbortSignal to cancel an async callback// during view animationconst abortController = new AbortController();
// Observe the View's animation statereactiveUtils.whenOnce( () => view?.animation, {signal: abortController.signal}) .then((animation) => { console.log(`View animation state is ${animation.state}`) });
// Cancel the async callbackconst someFunction = () => { abortController.abort();}
Working with truthy values
The when() and whenOnce() methods watch for truthy values, these are values that evaluate to true
in boolean contexts. To learn more about using truthy, visit this
MDN Web doc article. The snippets below use the Popup.visible property, which is a boolean.
// Observe changes on a boolean propertyconst viewElement = document.querySelector("arcgis-map");reactiveUtils.when(() => viewElement.popup?.visible, () => console.log("Truthy"));reactiveUtils.when(() => !viewElement.popup?.visible, () => console.log("Not truthy"));reactiveUtils.when(() => viewElement.popup?.visible === true, () => console.log("True"));reactiveUtils.when(() => viewElement.popup?.visible !== undefined, () => console.log("Defined"));reactiveUtils.when(() => viewElement.popup?.visible === undefined, () => console.log("Undefined"));Type definitions
ReactiveWatchOptions
- Type parameters
- <T = unknown>
Options used to configure how auto-tracking is performed and how the callback should be called.
ReactiveEqualityFunction
- Type parameters
- <T>
Function used to check whether two values are the same, in which case the watch callback isn't called.
Parameters
| Parameter | Type | Description | Required |
|---|---|---|---|
| newValue | T | The new value. | |
| oldValue | T | The old value. | |
- Returns
- boolean
Whether the new value is equal to the old value.
ReactiveWatchExpression
- Type parameters
- <T>
Function which is auto-tracked and should return a value to pass to the ReactiveWatchCallback
- Returns
- T
The new value.
ReactiveOnExpression
- Type parameters
- <T>
Function which is auto-tracked and should return an event target to which an event listener is to be added.
- Returns
- T
The event target.
ReactiveWatchCallback
- Type parameters
- <T>
Function to be called when a value changes.
Parameters
| Parameter | Type | Description | Required |
|---|---|---|---|
| newValue | T | The new value. | |
| oldValue | T | The old value. | |
- Returns
- void
ReactiveOnCallback
- Type parameters
- <T>
Function called to be called when an event is emitted or dispatched.
Parameters
| Parameter | Type | Description | Required |
|---|---|---|---|
| event | T | The event emitted by the target. | |
- Returns
- void
ReactiveListenerChangeCallback
- Type parameters
- <T>
Callback to be called when an event listener is added or removed.
Parameters
| Parameter | Type | Description | Required |
|---|---|---|---|
| target | T | The event target to which the listener was added or from which it was removed. | |
- Returns
- void
ReactiveOnOptions
- Type parameters
- <Target>
Options used to configure the behavior of on().
Functions
watch
- Type parameters
- <T, U extends T>
Tracks any properties accessed in the getValue function and calls the callback
when any of them change.
- Signature
-
watch <T, U extends T>(getValue: ReactiveWatchExpression<T>, callback: ReactiveWatchCallback<T>, options?: ReactiveWatchOptions<U>): ResourceHandle
Parameters
| Parameter | Type | Description | Required |
|---|---|---|---|
| getValue | Function used to get the current value. All accessed properties will be tracked. | | |
| callback | The function to call when there are changes. | | |
| options | Options used to configure how the tracking happens and how the callback is to be called. | |
- Returns
- ResourceHandle
A watch handle.
Examples
// Watching for changes in a boolean value// Equivalent to watchUtils.watch()const viewElement = document.querySelector("arcgis-map");reactiveUtils.watch( () => viewElement.popup?.visible, () => { console.log(`Popup visible: ${viewElement.popup.visible}`); });// Watching for changes within a Collectionconst viewElement = document.querySelector("arcgis-map");reactiveUtils.watch( () => viewElement.map.allLayers.length, () => { console.log(`Layer collection length changed: ${viewElement.map.allLayers.length}`); });// Watch for changes in a numerical value.// Providing `initial: true` in ReactiveWatchOptions// checks immediately after initialization// Equivalent to watchUtils.init()const viewElement = document.querySelector("arcgis-map");reactiveUtils.watch( () => viewElement.zoom, () => { console.log(`zoom changed to ${viewElement.zoom}`); }, { initial: true });// Watch properties from multiple sourcesconst viewElement = document.querySelector("arcgis-map");const handle = reactiveUtils.watch( () => [viewElement.stationary, viewElement.zoom], ([stationary, zoom]) => { // Only print the new zoom value when the map component is stationary if(stationary){ console.log(`Change in zoom level: ${zoom}`); } }); when
- Type parameters
- <T, U extends T>
Watches the value returned by the getValue function and calls the callback when it becomes truthy.
- Signature
-
when <T, U extends T>(getValue: ReactiveWatchExpression<T | null | undefined>, callback: (newValue: T, oldValue?: T) => void, options?: ReactiveWatchOptions<U>): ResourceHandle
Parameters
| Parameter | Type | Description | Required |
|---|---|---|---|
| getValue | ReactiveWatchExpression<T | null | undefined> | Function used to get the current value. All accessed properties will be tracked. | |
| callback | (newValue: T, oldValue?: T) => void | The function to call when the value becomes truthy. | |
| options | Options used to configure how the tracking happens and how the callback is to be called. | |
- Returns
- ResourceHandle
A watch handle.
Examples
// Observe when a boolean property becomes not truthy// Equivalent to watchUtils.whenFalse()reactiveUtils.when( () => !layerView.updating, () => { console.log("LayerView finished updating."); });// Observe when a boolean property becomes true// Equivalent to watchUtils.whenTrue()const viewElement = document.querySelector("arcgis-map");reactiveUtils.when( () => viewElement?.stationary === true, async () => { console.log("User is no longer interacting with the map"); await drawBuffer(); });// Observe a boolean property for truthiness.// Providing `once: true` in ReactiveWatchOptions// only fires the callback once// Equivalent to watchUtils.whenFalseOnce()const featuresComponent = document.querySelector("arcgis-features");reactiveUtils.when( () => !featuresComponent.closed, () => { console.log(`The features component is closed: ${featuresComponent.closed}`); }, { once: true }); on
- Type parameters
- <Target extends EventTarget | EventedMixin>
Watches the value returned by the getTarget function for changes and
automatically adds or removes an event listener for a given event, as
needed.
- Signature
-
on <Target extends EventTarget | EventedMixin>(getTarget: ReactiveOnExpression<Target | null | undefined>, eventName: string, callback: ReactiveOnCallback<any>, options?: ReactiveOnOptions<Target>): ResourceHandle
Parameters
| Parameter | Type | Description | Required |
|---|---|---|---|
| getTarget | ReactiveOnExpression<Target | null | undefined> | Function which returns the object to which the event listener is to be added. | |
| eventName | The name of the event to add a listener for. | | |
| callback | The event handler callback function. | | |
| options | Options used to configure how the tracking happens and how the callback is to be called. | |
- Returns
- ResourceHandle
A watch handle.
Examples
// Adds a click event on a map component when it changesconst viewElement = document.querySelector("arcgis-map");reactiveUtils.on( () => viewElement, "arcgisViewClick", (event) => { console.log("arcgisViewClick event emitted: ", event); });// Adds a drag event on a map component and adds a callback// to check when the listener is added and removed.// Providing `once: true` in the ReactiveListenerOptions// removes the event after first callback.const viewElement = document.querySelector("arcgis-map");reactiveUtils.on( () => viewElement, "arcgisViewDrag", (event) => { console.log(`Drag event emitted: ${event}`); }, { once: true, onListenerAdd: () => console.log("Drag listener added!"), onListenerRemove: () => console.log("Drag listener removed!") }); once
- Type parameters
- <T>
Tracks any properties being evaluated by the getValue function. When getValue changes, it
returns a promise containing the value. This method only tracks a single change.
- Signature
-
once <T>(getValue: ReactiveWatchExpression<T>, signal?: AbortSignal | AbortOptions | null | undefined): Promise<T>
Parameters
| Parameter | Type | Description | Required |
|---|---|---|---|
| getValue | Expression to be tracked. | | |
| signal | AbortSignal | AbortOptions | null | undefined | Abort signal which can be used to cancel the promise from resolving. | |
- Returns
- Promise
A promise which resolves when the tracked expression changes.
Examples
// Observe the first time a property equals a specific string value// Equivalent to watchUtils.once()reactiveUtils.once( () => featureLayer.loadStatus === "loaded") .then(() => { console.log("featureLayer loadStatus is loaded."); });// Use a comparison operator to observe a first time// difference in numerical valuesconst viewElement = document.querySelector("arcgis-map");const someFunction = async () => { await reactiveUtils.once(() => viewElement.zoom > 20); console.log("Zoom level is greater than 20!");}// Use a comparison operator and optional chaining to observe a// first time difference in numerical values.reactiveUtils.once( () => map?.allLayers?.length > 2) .then((value) => { console.log(`The map now has ${value} layers.`); }); whenOnce
- Type parameters
- <T>
Tracks any properties being evaluated by the getValue function. When getValue becomes truthy,
it returns a promise containing the value. This method only tracks a single change.
- Signature
-
whenOnce <T>(getValue: ReactiveWatchExpression<T | null | undefined>, signal?: (AbortSignal | AbortOptions) | null | undefined): Promise<T>
Parameters
| Parameter | Type | Description | Required |
|---|---|---|---|
| getValue | ReactiveWatchExpression<T | null | undefined> | Expression to be tracked. | |
| signal | (AbortSignal | AbortOptions) | null | undefined | Abort signal which can be used to cancel the promise from resolving. | |
- Returns
- Promise
A promise which resolves once the tracked expression becomes truthy.
Examples
// Check for the first time a property becomes truthy// Equivalent to watchUtils.whenOnce()const viewElement = document.querySelector("arcgis-map");reactiveUtils.whenOnce( () => viewElement.popup?.visible) .then(() => { console.log("Popup used for the first time"); });// Check for the first time a property becomes not truthy// Equivalent to watchUtils.whenFalseOnce()const someFunction = async () => { await reactiveUtils.whenOnce(() => !layerView.updating); console.log("LayerView is no longer updating");}// Check for the first time a property becomes truthy// And, use AbortController to potentially cancel the async callbackconst abortController = new AbortController();const viewElement = document.querySelector("arcgis-map");
// Observe the map component's updating statereactiveUtils.whenOnce( () => viewElement?.updating, {signal: abortController.signal}) .then((updating) => { console.log(`Map component's updating state is ${updating.state}`) });
// Cancel the async callbackconst someFunction = () => { abortController.abort();}