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.
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
Use dark colors for code blocks
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
28
29
30
31
32
33
34
35
36
37
38
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;`
export type 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
Use dark colors for code blocks
1
2
3
4
5
6
7
8
9
10
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")
exportclassCollectionextendsEventedMixin(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.
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
Use dark colors for code blocks
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
28
29
30
31
32
33
34
35
36
37
import Accessor = require("esri/core/Accessor");
import { subclass, property } from"esri/core/accessorSupport/decorators";
@subclass("esri.guide.Collection")
classCollectionextendsAccessor{
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()
getlength(): number {
returnthis._items.length;
}
setlength(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
Use dark colors for code blocks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Accessor = require("esri/core/Accessor");
import { subclass, property } from"esri/core/accessorSupport/decorators";
@subclass("esri.guide.Person")
classPersonextendsAccessor{
// 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
Use dark colors for code blocks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Accessor = require("esri/core/Accessor");
import { subclass, aliasOf } from"esri/core/accessorSupport/decorators";
@subclass("esri.guide.GroupLayer")
classGroupLayerextendsAccessor{
@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.
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
import Accessor = require("esri/core/Accessor");
import { subclass, property } from"esri/core/accessorSupport/decorators";
@subclass("esri.guide.Collection")
classCollectionextendsAccessor{
private _items: any[] = [];
@property({
readOnly: true })
getlength(): 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
Use dark colors for code blocks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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()
classGraphicsLayerextendsAccessor{
@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
Use dark colors for code blocks
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
28
29
30
31
32
33
34
import Accessor = require("esri/core/Accessor");
import { subclass, property, cast } from"esri/core/tsSupport/declare";
@subclass()
classColorextendsAccessor{
@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));
}
}
Additional information
Please refer to these additional links for further information: