Core
@vhb-map/core
is the main library that VHB developers can use to interact
with the Esri ArcGIS JavaScript API. core
borrows its name from its largest
dependency, which is @arcgis/core
. @arcgis/core
is the EcmaScript Module build
of the ArcGIS API.
It's important to note that the new does not mesh with the old! That is to say, if you try to use some of the Esri ESM build and some of the Esri legacy AMD build, you're going to have lots of trouble.
In general, VMC should be used for new Angular applications that have requirements
dictating the usage of the ArcGIS JavaScript API. For older applications,
you should either whole-sale migrate to this new library, or continue
to use @vhb/vhb-map
or @dustwhirl
in your applications.
It's worth nothing that @vhb/vhb-map
will not receive updates for new features,
but bugs and security issues will be patched.
Below, you'll find some general info regarding the library and some of its features/conventions. If you're looking to get started quickly, check out our guide showing how to render a simple map
Working with Core
Working with VMC should be pretty familiar for Angular Developers. It makes usage of Angular primitives to wrap the Esri API, which leads to clean, maintainable code.
Much like Angular Material, VMC exports all of its useful bits from "subentry-points". The reason for this is twofold. First and foremost, the ArcGIS JS API has some pretty large files. By isolating the Esri code that we use in separate entry points, we avoid importing "the kitchen sink". Over-importing the ArcGIS JS API will quickly lead to a gigantic production bundle. Secondly, it's for organizational purposes. Working with a library (as a consumer and an author) that has clearly defined boundaries leads to less confusion and more productivity.
Naming Conventions
Events
The ArcGIS JavaScript API implements events on many of its classes. In particular, the
Esri MapView
and
Esri SceneView
objects are full of events related to user interactions.
MapView
events are available on the
WebMapComponent
and MapComponent
as Angular events. SceneView
events are available on the SceneComponent
and WebSceneComponent
as Angular events. Widget and Layer events are available on their corresponding directive or component.
Events in the ArcGIS JS API are exported with kebab-case
strings. Since Angular events are
instantiated as class properties, the names are different. The kebab-case
strings are translated
into lowerCamelCase
and decorated with the Angular
Output decorator.
Additionally, the VMC names are prefixed with the Esri object's name that's responsible for the event.
A major exception to this naming convention is the MapView
and SceneView
events!!! The events are
simply prefaced with view
, NOT mapView
or sceneView
.
A major exception to this naming convention is the Layer
events!!! The events are
simply prefaced with layer
, NOT csvLayer
or featureLayer
.
Here are some example translations:
Esri API Class | Esri Event | VMC Event |
---|---|---|
MapView |
click |
viewClick |
MapView |
double-click |
viewDoubleClick |
SceneView |
layerview-create |
viewLayerViewCreate |
Sketch |
create |
sketchCreate |
CSVLayer |
layerview-create-error |
layerViewCreateError |
Additionally, all directives and components instantiate ArcGIS JavaScript API objects will event those objects
at two different moments in the lifecycle. There are a series of added
events and a series of ready
events.
Added
events fire when the object has been instantiated and added into the MapView
.
Ready
events fire when the object has been instantiated, added to the MapView
, and the object's
when
callback has fired.
These events are intended to be your source for the ArcGIS JS API Objects, so that you can grab
a reference to them and manipulate them as needed. The added
and ready
events
are typically prefixed with lowerCamelCase
of the object that is being evented.
Here is a quick example of using the ready
variety of events:
<vmc-map
class="map-container"
[basemap]="basemap"
[zoom]="zoom"
[center]="center"
(mapViewReady)="onMapViewReady($event)"
></vmc-map>
import { Component, Injectable } from '@angular/core';
import Graphic from '@arcgis/core/Graphic';
import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer';
@Injectable()
export class GraphicsLayerService extends GraphicsLayer {
constructor() {
super();
super.title = 'super extendable';
}
}
@Component({
selector: 'vhb-map-showcase',
templateUrl: './map-showcase.component.html',
styleUrls: ['./map-showcase.component.css'],
providers: [GraphicsLayerService],
})
export class MapShowcaseComponent {
public basemap = 'streets-vector';
public center = [-99, 37];
public zoom = 3;
constructor(private graphicsLayerService: GraphicsLayerService) {}
onMapViewReady(mapView: __esri.MapView): void {
const graphic = new Graphic({
geometry: {
type: 'point',
spatialReference: { wkid: 3857 },
x: -8250497.3282590415,
y: 5414076.500315,
} as any,
symbol: {
type: 'simple-marker',
color: [255, 255, 255, 255],
angle: 0,
xoffset: 0,
yoffset: 0,
size: 12,
style: 'triangle',
outline: {
type: 'simple-line',
color: [50, 50, 50, 255],
width: 1,
style: 'solid',
},
},
attributes: {},
} as any);
this.graphicsLayerService.add(graphic);
this.graphicsLayerService.listMode = 'show';
mapView.map.add(this.graphicsLayerService);
}
}
Modules
All of the modules that are exported in the various subentry points of @vhb-map/core
are
prefaced with Vmc
. For example, to initialize a
WebMap,
you'll need to import the VmcWebMapModule
from @vhb-map/core/web-map
.
This convention also applies to the various Esri Widgets that are integrated into VMC. For example, to import the LayerList Widget and the WebMap, you'll use the following line of code in your application's feature module:
import { VmcWebMapModule } from '@vhb-map/core/web-map';
import { VmcLayerListModule } from '@vhb-map/core/layer-list';
Components and Directives
Whether a particular piece of UI is using a directive or a component is really an implementation detail of the library, and is of no concern to the developer using the library.
All components and directives are exported with a "component-like selector", meaning
they'll be used the same way in your templates. Similar to how Angular Material exports
all of their components with the mat-
prefix, VHB Map Core exports its components
with the vmc-
prefix.
The rest of the selector is based on the Esri name for the widget or other API primitive.
While Esri exports its classes with CapitalCamelCase
(e.g. FeatureLayer
), those
classes are turned to lower kebab-case
in the selectors.
You can see the naming convention at work in this HTML template example:
<vmc-web-map>
<vmc-basemap-toggle [nextBasemap]="nextBasemap"></vmc-basemap-toggle>
<vmc-basemap-gallery [expand]="true"></vmc-basemap-gallery>
<vmc-legend [expand]="true"></vmc-legend>
<vmc-layer-list [expand]="true"></vmc-layer-list>
<vmc-feature-layer
[properties]="{
url:
'https://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer/3'
}"
></vmc-feature-layer>
<vmc-home></vmc-home>
<vmc-compass></vmc-compass>
<vmc-fullscreen></vmc-fullscreen>
<vmc-measure></vmc-measure>
<vmc-search></vmc-search>
<vmc-bookmarks
[expand]="true"
[properties]="{ editingEnabled: true }"
(bookmarkEdit)="onBookmarkEdit($event)"
></vmc-bookmarks>
<vmc-sketch
#sketch="vmcSketch"
[expand]="true"
[layerProperties]="{ listMode: 'show', title: 'my graphics layer' }"
(sketchReady)="sketch.sketchExpand?.expand()"
></vmc-sketch>
<vmc-coordinate-conversion [expand]="true"></vmc-coordinate-conversion>
</vmc-web-map>
Components and Directives
All of the components and directives that are exported from the library exclude the Vmc
prefix. They are exported with the Esri name as the main identifier and are
named according to the
Angular style guide.
This means that directives will be suffixed with the word Directive
, components will
be suffixed with the word Component
, and their files on disk will have the .
notation
prescribed by Angular. The .
notation is really more of a library implementation detail,
as the dots do not come through on the public export path.
Putting that in to practice and using the
Esri FeatureLayer
and a custom implementation of the
Esri CoordinateConversion
widget
as an example, we have the following name:
import { CoordinateConversionComponent } from '@vhb-map/core/coordinate-conversion';
import { FeatureLayerDirective } from '@vhb-map/core/feature-layer';
Services
Most directives and components in the library have an accompanying service. While the
implementations vary, in general, each service has a single method that adds a widget
to an Esri Map
object. With a configuration parameter, the service can be instructed
to place the Esri widget in an
Esri Expand
widget.
Here are some example service imports:
import { MapService } from '@vhb-map/core/map';
import { FeatureTableService } from '@vhb-map/core/feature-table';
For @vhb-map/core
Developers
This core
directory contains all of the sub-entry points for the library. The core
directory itself does not export anything useful, but is merely an organizational placeholder. This design is molded after that of @angular/material
.
Secondary Entry Points
@vhb-map/core
uses subentry points. Subentry points help to further modularize the library, similar to @angular/material
. This extra modularity helps the Angular Compiler and build tooling more effectively tree shake our application. Further, deeper analyses of the dependency graph are possible, allowing webpack
to more effectively share code between bundles.
Consider a single entry point library being used in a lazy loaded application. The application uses the library in two of the routes. The library exports 3 components. 1 is used in app route a, the other is used in app route b, and the third is not used. The Angular tooling will end up building the library as a bundle, presumably the largest bundle, then 2 little bundles with the app code for route a and b.
Now consider the same application and the same library, but with secondary entry points. The Angular tooling will generate 2 bundles, one with app code for route a and lib code for route a, and another with app code for route b and lib code for route b. This is more efficient than the single entry point option.
@vhb-map/core
Tooling
To generate a new subentry point:
# Change directories to:
cd ./libs/core/src
# Run the schematic:
ng g ng-samurai:generate-subentry web-map --project core --generate-component false --dry-run
Further Reading
https://medium.com/angular-in-depth/improve-spa-performance-by-splitting-your-angular-libraries-in-multiple-chunks-8c68103692d0 https://medium.com/@tomastrajan/the-best-way-to-architect-your-angular-libraries-87959301d3d3 https://olofens.medium.com/secondary-entry-points-in-an-angular-nx-library-8d01a80634cc https://github.com/angular/components/tree/master/src/google-maps