This guide provides examples of common Accessor usage patterns.
The code snippets below include @arcgis/core ES modules (ESM) for use in local builds, and CDN examples in vanilla JavaScript. @arcgis/core includes TypeScript typings. Typings are not available for use with the CDN.
Extend Accessor
Many classes in the core API extend the Accessor class. These classes can expose properties that may have unique characteristics, such as being read-only or computed.
Create a simple subclass
When building applications with JavaScript use the createSubclass() method which automatically calls super(). When building with TypeScript, use the @subclass() decorator along with the extends keyword.
Also when using TypeScript decorators such as @subclass(), it may be required to set the useDefineForClassFields flag to false for backwards compatibility. More information is available in the TSConfig Reference.
The declaredClass property is specified as a string in the constructor, and helps the API differentiate between the existing class that you are extending and the custom one you are building. In the API, declaredClass is readonly. This property provides the same functionality in both JavaScript and TypeScript. However, as shown in the example below, the implementations are different.
import Accessor from "@arcgis/core/core/Accessor.js";import { subclass } from "@arcgis/core/core/accessorSupport/decorators.js";
// declaredClass is "custom.Color"@subclass("custom.Color")class Color extends Accessor { // ...}const [Accessor] = await $arcgis.import(["@arcgis/core/Accessor"]);
const Color = Accessor.createSubclass({ declaredClass: "custom.Color"});Mixins with Accessor
The
First we define our EventedMixin to add an event system to a class.
import Accessor from "@arcgis/core/core/Accessor.js";import { subclass } from "@arcgis/core/core/accessorSupport/decorators.js";
// A type to represent a constructor functiontype Constructor<T = object> = new (...args: any[]) => T;
// A type to represent a mixin function// See for more details https://www.bryntum.com/blog/the-mixin-pattern-in-typescript-all-you-need-to-know/type Mixin<T extends (...input: any[]) => any> = InstanceType<ReturnType<T>>;
// TBase extends Constructor<Accessor> indicating that `EventedMixin`// expects the base class to extend `Accessor`.export const EventedMixin = <TBase extends Constructor<Accessor>>(Base: TBase) => {
@subclass("custom.EventedMixin") return class Evented extends Base {
// A first function defined by the mixin emit(type: string, event?: any): boolean { // ... }
// Another function defined by the mixin on(type: string, listener: (event: any) => void): IHandle { // ... } }}
// define the type of the mixin. This is useful to type properties that extends this mixin// eg: `myProperty: EventedMixin;`export type EventedMixinType = Mixin<typeof EventedMixin>;const [Accessor] = await $arcgis.import(["@arcgis/core/Accessor"]);
// A mixin is a function that returns a class extending the `Base` superclass// with extra functionalities.const EventedMixin = (Base) => {
return Base.createSubclass({
// A first function defined by the mixin emit: function (type, event) { // ... },
// Another function defined by the mixin on: function (type, listener) { // ... } });};In this example we create a super class that extends Accessor and adds capabilities from EventedMixin. The Collection class then extends the final subclass.
import Accessor from "@arcgis/core/core/Accessor.js";import { subclass } from "@arcgis/core/core/accessorSupport/decorators";
// import the newly created custom mixinimport { EventedMixin } from "custom/EventedMixin.ts";
@subclass("custom.Collection")export class Collection extends EventedMixin(Accessor) { // Collection extends a super class composed of Accessor and EventedMixin.}const [Accessor, CustomMixin] = await $arcgis.import([ "@arcgis/core/Accessor.js", "custom/EventedMixin"]);
const Collection = EventedMixin(Accessor).createSubclass({ declaredClass: "custom.Collection"});
return Collection;Properties
Define a simple property
Use the following syntax for creating simple properties that do not require any additional behavior. You can define both default values and types for primitive property values. If working with TypeScript, default property values can be set in the constructor.
import Accessor from "@arcgis/core/core/Accessor.js";import { subclass, property } from "@arcgis/core/core/accessorSupport/decorators.js";
@subclass("custom.Color")class Color extends Accessor { @property() r: number = 255;
@property() g: number = 255;
@property() b: number = 255;
@property() a: number = 1;}const Accessor = await $arcgis.import("@arcgis/core/Accessor.js");
const Color = Accessor.createSubclass({ declaredClass: "custom.Color",
constructor: function() { this.r = 255; this.g = 255; this.b = 255; this.a = 1; },
properties: { r: {}, g: {}, b: {}, a: {} }Define custom getter and setter
There may be times when you may need to verify, validate, or transform values set on a property. You may also need to do additional (synchronous) work when a property is being set. The this.set() method is inherited from Accessor. The following snippets show this.
import Accessor from "@arcgis/core/core/Accessor.js";import { subclass, property } from "@arcgis/core/core/accessorSupport/decorators.js";
@subclass("custom.Collection")class Collection extends Accessor { private _items: any[] = [];
// Example: Define a custom property getter. // Accessor caches the values returned by the getters. // At this point `length` will never change. // See the "Notify a property change" section @property() get length(): number { return this._items.length; }
set length(value: number) { // Example: perform validation if (value <= 0) { throw new Error(`value of length not valid: ${value}`); }
// internally you can access the cached value of `length` using `_get`. const oldValue = this._get<number>("length");
if (oldValue !== value) { // a setter has to update the value from the cache this._set("length", value);
// Example: perform additional work when the length changes this._items.length = value; } }}const Accessor = await $arcgis.import("@arcgis/core/Accessor.js");
const Collection = Accessor.createSubclass({ declaredClass: "custom.Collection",
constructor() { this._items = []; },
_items: null,
properties: { length: { // Example: Define a custom property getter. // Accessor caches the values returned by the getters. // At this point `length` will never change. // See the "Notify a property change" section get: function() { return this._items.length; }, set: function(value) { // Example: perform validation if (value <= 0) { throw new Error(`value of length not valid: ${value}`); }
// internally you can access the cached value of `length` using `_get`. const oldValue = this._get("length");
if (oldValue !== value) { // a setter has to update the value from the cache this._set("length", value);
// Example: perform additional work when the length changes this._items.length = value; } } } }});Define a read-only property
The following syntax shows how to set a read-only property. The this.set() method is inherited from Accessor.
import Accessor from "@arcgis/core/core/Accessor.js";import { subclass, property } from "@arcgis/core/core/accessorSupport/decorators.js";
@subclass("custom.Person")class Person extends Accessor { // Example: read-only property may not be externally set @property({ readOnly: true }) firstName: string;
@property({ readOnly: true }) lastName: string;
updateName(firstName: string, lastName: string): void { // We may still update the read-only property internally, which will change // the property and notify changes to watchers this._set({ firstName, lastName }); }}const Accessor = await $arcgis.import("@arcgis/core/Accessor.js");
const Person = Accessor.createSubclass({ declaredClass: "custom.Person",
properties: { // Example: read-only property may not be externally set firstName: { readOnly: true },
lastName: { readOnly: true } },
updateName: function(firstName, lastName) { // We may still update the read-only property internally, which will change // the property and notify changes to watchers this._set({ firstName: firstName, lastName: lastName }); }});Define a proxy property
These snippets show how to create a two-way binding on an inner property.
import Accessor from "@arcgis/core/core/Accessor.js";import Collection from "@arcgis/core/core/Collection.js";import { subclass, property, aliasOf } from "@arcgis/core/core/accessorSupport/decorators.js";
@subclass("custom.GroupLayer")class GroupLayer extends Accessor { @property() sublayers: Collection = new Collection();
// Define a property that reflects one in another object. @property({ aliasOf: "sublayers.length" }) length: number;
// Alternatively you can use the `@aliasOf` decorator // @aliasOf // length: number
// You can also proxy a method from another object. @aliasOf("sublayers.add") add: (item: any) => void;}const Accessor = await $arcgis.import("@arcgis/core/Accessor.js");
const GroupLayer = Accessor.createSubclass({ declaredClass: "custom.GroupLayer",
constructor() { this.sublayers = new Collection(); },
properties: { sublayers: {},
// Define a property that reflects one in another object. length: { aliasOf: "sublayers.length" },
// You can also proxy a method from another object. add: { aliasOf: "sublayers.add" } }});Computed properties
Define a computed property
You may need to use this when a property value depends on numerous other properties. These properties are always read-only.
import Accessor from "@arcgis/core/core/Accessor.js";import { subclass, property } from "@arcgis/core/core/accessorSupport/decorators.js";
@subclass()class Person extends Accessor { @property() firstName: string;
@property() lastName: string;
@property({ readOnly: true }) get fullName(): string { return `${this.firstName} ${this.lastName}`; }}const [Accessor] = await $arcgis.import(["@arcgis/core/Accessor"]);
const Person = Accessor.createSubclass({ properties: { firstName: {}, lastName: {},
fullName: { readOnly: true, get: function() { return this.firstName + " " + this.lastName; } } }});Define a writable computed property
import Accessor from "@arcgis/core/core/Accessor.js";import { subclass, property } from "@arcgis/core/core/accessorSupport/decorators.js";
@subclass()class Person extends Accessor { @property() firstName: string;
@property() lastName: string;
@property() get fullName(): string { return `${this.firstName} ${this.lastName}`; }
set fullName(value: string) { if (value === "") { this._set("firstName", null); this._set("lastName", null);
return; }
const [firstName, lastName] = value.split(" "); this._set("firstName", firstName); this._set("lastName", lastName); }}const [Accessor] = await $arcgis.import(["@arcgis/core/Accessor"]);
const Person = Accessor.createSubclass({ properties: { firstName: {}, lastName: {},
fullName: { readOnly: true, get: function() { return this.firstName + " " + this.lastName; },
set: function(value) { if (!value) { this._set("firstName", null); this._set("lastName", null); this._set("fullName", null);
return; }
let split = value.split(" "); this._set("firstName", split[0]); this._set("lastName", split[1]); this._set("fullName", value); } } }});Notify a property change
Sometimes properties cannot notify when changed. Accessor has an internal method to notify of any changes, and it marks the property as dirty. The next time the property is accessed its value is re-evaluated.
import Accessor from "@arcgis/core/core/Accessor.js";import { subclass, property } from "@arcgis/core/core/accessorSupport/decorators.js";
@subclass("custom.Collection")class Collection extends Accessor { private _items: any[] = [];
@property({ readOnly: true }) get length(): number { return this._items.length; }
add(item: any): void { this._items.push(item);
// We know the value of `length` is changed. // Notify so that at next access, the getter will be invoked this.notifyChange("length"); }}const [Accessor] = await $arcgis.import(["@arcgis/core/Accessor"]);
const Collection = Accessor.createSubclass({ declaredClass: "custom.Collection",
constructor() { this._items = []; },
_items: null,
properties: { length: { get: function() { return this._items.length; } } },
add: function(item) { this._items.push(item);
// We know the value of `length` is changed. // Notify so that at next access, the getter will be invoked this.notifyChange("length"); }
});Autocast
Define the property type
It is possible to define a type for a class property.
import Graphic from "@arcgis/core/Graphic.js";import Accessor from "@arcgis/core/core/Accessor.js";import Collection from "@arcgis/core/core/Collection.js";import { subclass, property } from "@arcgis/core/core/accessorSupport/decorators.js";
@subclass()class GraphicsLayer extends Accessor { @property({ // Define the type of the collection of Graphics // When the property is set with an array, // the collection constructor will automatically be called type: Collection.ofType(Graphic) }) graphics: Collection<Graphic>;} const [Accessor, Collection, Graphic] = await $arcgis.import([ "@arcgis/core/Accessor.js", "@arcgis/core/core/Collection.js", "@arcgis/core/Graphic.js" ]);
const GraphicsLayer = Accessor.createSubclass({ properties: { graphics: { type: Collection.ofType(Graphic) } }});Define a method to cast a property
Sometimes you need to validate a property’s value type when it is being set. A good example of this is having well-known, preset, names for specific values, such as map.basemap = "streets-vector".
The type metadata automatically creates an appropriate cast for Accessor and primitive types if it is not already set.
import Accessor from "@arcgis/core/core/Accessor.js";import { subclass, property, cast } from "@arcgis/core/core/accessorSupport/decorators.js";
@subclass()class Color extends Accessor { @property() r: number = 0;
@property() g: number = 0;
@property() b: number = 0;
@property() a: number = 1;
@cast("r") @cast("g") @cast("b") protected castComponent(value: number): number { // cast method to clamp the value that // will be set on r, g or b between 0 and 255 return Math.max(0, Math.min(255, value)); }
@cast("a") protected castAlpha(value: number): number { // cast method to clamp the value that // will be set on a between 0 and 1 return Math.max(0, Math.min(1, value)); }}const Accessor = await $arcgis.import("@arcgis/core/Accessor.js");
const Colors = Accessor.createSubclass({ castComponent: (value) => { // cast method to clamp the value that // will be set on r, g or b between 0 and 255 return Math.max(0, Math.min(255, value)); },
castAlpha: (value) => { // cast method to clamp the value that // will be set on a between 0 and 1 return Math.max(0, Math.min(1, value)); },
properties: { r: { value: 255, cast: this.castComponent }, g: { value: 255, cast: this.castComponent }, b: { value: 255, cast: this.castComponent }, a: { value: 1, cast: this.castAlpha } }});