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.

Further Reading

@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