Accessor aims to make developing classes easy by providing a mechanism to get, set, and watch properties.
This guide provides a guideline for common Accessor usage patterns. Please follow the links below to get further information on how to implement classes derived from Accessor. Please see the working with properties guide topic for additional information on Accessor properties.
If working in TypeScript, you will want to install the ArcGIS API for JavaScript 4.x type definitions. You can access these typings with its command line syntax at the jsapi-resources Github repository.
Extend Accessor
Many classes in the API extend the Accessor class. These classes can expose watchable properties that may have unique characteristics, such as being read-only or computed. Under the hood, Accessor uses dojo/_base/declare to create classes.
Create a simple subclass
The /// comments are compiler directives for TypeScript. They let the TypeScript compiler know to include additional files. In this case, it specifies helper modules for the TypeScript decorators and extending classes.
Simple subclass - TSSimple subclass - JS
import Accessor = require("esri/core/Accessor");
import { subclass } from"esri/core/accessorSupport/decorators";
@subclass("esri.guide.Color")
class Color extends Accessor {
}
Extend multiple classes - Deprecated
Extending multiple classes is deprecated at 4.13, and support removed at 4.16. If apps are not upgraded, a warning message will appear in the browser console at 4.14 and 4.15, and classes that leverage the API class framework and multiple inheritance will stop working at version 4.16. This feature relied on dojo/_base/declare which is removed from the JavaScript API. Instead see how to create mixins with TypeScript and JavaScript.
When extending multiple classes using the declared helper, you can take advantage of declaration merging by giving the interface the same name as the class.
The ArcGIS API for JavaScript uses mixins to build its classes. Read this excellent article that goes deep dive on mixins with TypeScript.
First we define our EventedMixin to add an event system to a class.
Defining an Accessor mixin - TSDefining an Accessor mixin - JS
import Accessor = require("esri/core/Accessor");
import { subclass } from"esri/core/accessorSupport/decorators";
// 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> indicates that `EventedMixin`// expects the base class to extend `Accessor`, for example to be able to use the `watch` method.exportconst EventedMixin = <TBase extends Constructor<Accessor>>(Base: TBase) => {
@subclass("esri.guide.Evented")
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 {
// ... }
}
return Evented;
}
// define the type of the mixin. This is useful to type properties that extends this mixin// eg: `myProperty: EventedMixin;`exporttype EventedMixin = Mixin<typeof EventedMixin>;
A mixin is a function that creates the super class for final subclass. In this example we create a super class that extends Accessor and adds capabilities from EventedMixin. The Collection class then extends the final subclass.
Using an Accessor mixin - TSUsing an Accessor mixin - JS
import Accessor = require("esri/core/Accessor");
import { subclass } from"esri/core/accessorSupport/decorators";
// import the newly created mixinimport { EventedMixin } from"esri/guide/EventedMixin";
@subclass("esri.guide.Collection")
exportclass Collection extends EventedMixin(Accessor) {
// Collection extends a super class composed of Accessor and EventedMixin.}
Properties
Define a simple property
The following syntax should be used when you want to have a simple, watchable, property that does 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.
Simple property - TSSimple property - JS
import Accessor = require("esri/core/Accessor");
import { subclass, property } from"esri/core/accessorSupport/decorators";
@subclass("esri.guide.Color")
class Color extends Accessor {
@property()
r: number = 255;
@property()
g: number = 255;
@property()
b: number = 255;
@property()
a: number = 1;
}
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 following snippets show this.
Setter property - TSSetter property - JS
import Accessor = require("esri/core/Accessor");
import { subclass, property } from"esri/core/accessorSupport/decorators";
@subclass("esri.guide.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 {
returnthis._items.length;
}
set length(value: number) {
// Example: perform validationif (value <= 0) {
thrownewError(`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 cachethis._set("length", value);
// Example: perform additional work when the length changesthis._items.length = value;
}
}
}
Define a read-only property
The following syntax shows how to set a read-only property.
Read-only - TSRead-only - JS
import Accessor = require("esri/core/Accessor");
import { subclass, property } from"esri/core/accessorSupport/decorators";
@subclass("esri.guide.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 watchersthis._set({
firstName: firstName,
lastName: lastName
});
}
}
Define a proxy property
Sometimes you need to proxy a property when both reading and writing, in addition to possibly performing a transformation on the value. For example, exposing an inner member property.
Proxy property - TSProxy property - JS
import Accessor = require("esri/core/Accessor");
import { subclass, aliasOf } from"esri/core/accessorSupport/decorators";
@subclass("esri.guide.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;
}
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.
Sometimes properties cannot notify when changed. Accessor has an internal method to notify of any changes. This will mark the property as dirty. The next time the property is accessed its value is re-evaluated.
import Accessor = require("esri/core/Accessor");
import { subclass, property } from"esri/core/accessorSupport/decorators";
@subclass("esri.guide.Collection")
class Collection extends Accessor {
private _items: any[] = [];
@property({
readOnly: true })
get length(): number {
returnthis._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 invokedthis.notifyChange("length");
}
}
Autocast
Define the property type
It is possible to define a type for a class' property.
Define the property type - TSDefine the property type - JS
import Graphic = require("esri/Graphic");
import Accessor = require("esri/core/Accessor");
import Collection = require("esri/core/Collection");
import { subclass, property } from"esri/core/accessorSupport/decorators";
@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 calledtype: Collection.ofType(Graphic)
})
graphics: Collection<Graphic>;
}
Define a method to cast a property
Sometimes you need to ensure 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.
Define a casting method - TSDefine a casting method - JS
import Accessor = require("esri/core/Accessor");
import { subclass, property, cast } from"esri/core/tsSupport/declare";
@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 that clamp the value that// will be set on r, g or b between 0 and 255returnMath.max(0, Math.min(255, value));
}
@cast("a")
protected castAlpha(value: number): number {
// cast method that clamp the value that// will be set on a between 0 and 1returnMath.max(0, Math.min(1, value));
}
}
Define the parameters type from a method - Deprecated
Defining parameter type from a method is deprecated at 4.14. Parameter decorators is not part of the current JavaScript Decorators standardization proposal, so the support will be preemptively removed from the JavaScript API.
It is possible to autocast parameters of a method. In this case, the developer is not required to import the class of the parameter and instantiate it.