Skip to content

Instantly share code, notes, and snippets.

@hhkaos
Last active May 1, 2024 17:28
Show Gist options
  • Save hhkaos/e9418ac1b9020eb7305c253aafc40228 to your computer and use it in GitHub Desktop.
Save hhkaos/e9418ac1b9020eb7305c253aafc40228 to your computer and use it in GitHub Desktop.
From Zero to Newbie with ArcGIS API for JS and TypeScript

From zero to newbie with ArcGIS API for JS and TS.

Hello TypeScript demo

Initialize a typescript project:

npm init -y
npm i typescript --save-dev

Create app/index.ts:

const username = "hhkaos";

const helloName = (name) => {
  console.log(name);
};

helloName(username);

Manually compile the file: ./node_modules/typescript/bin/tsc app/index.ts

This will generate an app/index.js. Open and check the generated code.

Manually run the compiled file node app/index.js.

Any JS code is valid TS code (== TS is a superset of JS) but when using TypeScript, it will force us:

  • To declare every variable
  • To keep inmutable the type of each variable (unless you explicitly define it as any type).

Bellow we will learn how to configure the TS compiler to be more or less flexible/strict.

Very basic intro to some TypeScript capabilities

TypeScript allows us to define "contracts" in our code like:

  • Which is the type of a variable
  • Which properties must have and object
  • In a function, the type of each parameter and the type returned
  • etc.

Go to the TypeScript playground and try this:

// Basic types
var num: number = 2;
var today: Date = new Date(); 
var isEmpty: boolean = false; 
var colors: string[] = ['red', 'green', 'blue']; 

// Functions
const add = (a, b) => { // ES2015 arrow function
    return a + b;
};

// add() receives a and b of type number, and return a number
const add = (a: number, b: number): number => { 
    return a + b;
};

// function with optional parameters
const printHello = (text?: string): void => { 
    console.log(`Hello ${text? text: "World"}`);
};

printHello();
printHello("Raul");

// function allowing multiple types for a parameter
function getlength(value: number | string): number {
  if(typeof value === "number"){
    return value.toString().length;
  }else{
    return value.length;
  } 
}

console.log(getlength("Raul"));
console.log(getlength(3.141516));

// Interfaces
interface Point{
  x: number,
  y: number
}

function printPoint(p: Point){
  console.log(`x = ${p.x}, y= ${p.y}`);
}

const x = {
  x: 123,
  z: 123
}

printPoint(x);

Now check the type definitions (.D.TS) generated.

That file will provide to Visual Studio Code (and maybe other IDEs) enough knowledge to:

  • Identify some coding errors while we are writing
  • Extend the JS/CSS/HTML code autocompletion adding it also our own code and third party libraries.

Both things will save us time.

Change tsconfig.json and package.json defaults

Create tsconfig.json (TypeScript compiler options)

./node_modules/typescript/bin/tsc --init

Check error disapears but appears any. Edit tsconfig.json:

"noImplicitAny": false

Test options in the TypeScript Playground:

  • Target: ES5 vs ES2017
  • Type Checking
    • noImplicitAny: true, the code bellow will fail:

      function fn(s) {
        console.log(s.subtr(3));
      }
      fn(42);
    • strictFunctionTypes:true

      function fn(x: string) {
        console.log("Hello, " + x.toLowerCase());
      }
      
      type StringOrNumberFunc = (ns: string | number) => void;
      
      let func: StringOrNumberFunc = fn;
      func(10);

To automatically recompile TypeScript, add on package.json:

"scripts": {
    "dev": "tsc -w"
}

This will run the compiler in watch mode. Execute it with: npm run dev

Import basic local class

Now, we are going to see a simple examplo of how we can modularize our code to better structure it.

Note: understanding how classes works and how TypeScript works will help us better understand how the API is structured.

For that lets create a new TS file declaring a ES6 Class: app/Map.ts:

export class Map{
  basemap;
  center;
  
  constructor(properties){
    this.basemap = properties.basemap || "topo";
    this.center = properties.center || {x: 0, y: 0};
  }

  get(property){
    return this[property];
  }
}

Check how TS translate it to prototype because we are using "target": "es5", in the tsconfig.json.

Now import the class to the index.ts and use it:

import { Map } from "./Map" ;

// ...

let myMap = new Map({
  basemap: "streets"
});

console.log(myMap.get("basemap"));
console.log(myMap.center);

Run it with node app/index.js and see how it works.

Import ArcGIS API for JS: AMD + CDN

Now it's type to get prepare to leave node and compile our code to be browser-ready.

First update the TypeScript config (tsconfig.json) to compile to AMD modules editing, and use "es2019" (recommended by the ArcGIS API for JS team).

Caveat: if you prefer to use local ESM, check other starter apps here.

"module": "amd",
"target": "es2019", 

Notice how index.js now uses require to load the modules.

Next, remove the lastest adds in the index.ts:

// import { Map } from "./Map" ;

// ...

// let myMap = new Map({
//   basemap: "streets"
// });

// console.log(myMap.get("basemap"));
// console.log(myMap.center);

And add:

import MapView from "esri/views/MapView";

After addind that line you will get the following error: Cannot find module 'esri/views/MapView' or its corresponding type declarations.(2307)

It is because any time you use a third party library, you have to install the TypeScript type definitions. For the ArcGIS JS API you do it like this:

npm i @types/arcgis-js-api --save-dev

More info about type definitions.

Now you will see that it doesn't fail.

Note: the path of the MapView needs to be the AMD path because we set the compile to AMD.

Next, add the following code to index.ts:

new MapView({
  map: {
    basemap: "streets-navigation-vector"
  },
  container: "viewDiv",
  center: [ -118.244, 34.052],
  zoom: 12
});

Type definitions and code autocompletion

Realize that now when leaving the cursor over a class name or a property, thanks to the type definitions:

  • A tooltip appears with:
    • The definition/description of the class/property
    • And accepted parameters or values
    • Links to the ArcGIS for JavaScript API Reference
  • Each time we add a dot (.) after a variable or property of our map/layer/etc. VS code will show a list of properties and methods available

For example:

  • Over MapView class: links to the Map and MapView classes and that it expects just one argument MapViewProperties (which is optional)
  • Over the property center: links to the documentation of the property in the API Reference but also a link to the Point class. It expects an instance of a Point, and object with type: "point", and array of numbers or undefined.
  • Over the container property: a link to the documentation of the property in the API Reference and that it expects an string, HTMLDivElement or undefined.
  • After view. a list of properties and methods are displayed (scrollable).

Note: pressing Ctrl (Win) / CMD (Mac), you will be able to browse through the type definition. You can consider it also as a source of truth.

Now it's time to execute the code client-side.

Execute code client-side

Now create an index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="https://js.arcgis.com/4.20/esri/css/main.css">
  <script src="https://js.arcgis.com/4.20"></script>
  <style>
    html,
    body,
    #viewDiv {
      padding: 0;
      margin: 0;
      height: 100%;
      width: 100%;
    }
  </style>
</head>
<body>
  <div id="viewDiv"></div>
  <script src="app/index.js"></script>
</body>
</html>

If you try to load it in a browser you wil notice it doesn't do anything, it is because we need to load it using require (remember, we are using AMD).

So lets use the dojoConfig included in the AMD of this version of the ArcGIS JS API to be able to use require(["app/index"]); to load our code.

Add before the <script> tag of the ArcIGS JS API:

<script>
  var locationPath = location.pathname.replace(/\/[^\/]*$/, "");
  window.dojoConfig = {
      packages: [{
          name: "app",
          location: locationPath + "/app"
      }]
  };
</script>

And now replace:

  • This line: <script src="index.js"></script>
  • For this line: <script>require(["app/index"]);</script>

And voilà! there we have it.

Migrate a sample app

We are going to migrate this JSAPI sample. Copy it and replace the index.ts.

First we will have to replace the requires:

import Map from "esri/Map";
import MapView from "esri/views/MapView";
import GroupLayer from "esri/layers/GroupLayer";
import FeatureLayer from "esri/layers/FeatureLayer";
import ImageryLayer from "esri/layers/ImageryLayer";
import VectorTileLayer from "esri/layers/VectorTileLayer";

Reindent and then remove (or add // @ts-ignore) the invalid property:

lastestWkid: 102003

Fix the problem with the autocast (known limitation of TS):

import SimpleFillSymbol from "esri/symbols/SimpleFillSymbol";

// ...

symbol: new SimpleFillSymbol({
          color: "black"
        })

Benefit from using VS code snippets

Now we will see how to use the "ArcGIS API for JavaScript Snippets" extension and how to create custom "Users snippets".

Use search snippet to add the search widget:

import Search from "esri/widgets/Search";

const searchWidget = new Search({
  view: view
});
  
view.ui.add(searchWidget, "top-right");

Typescript resources

Resources:

Summary

  • How to compile TypeScript file (*.ts) to JavaScript (*.js)
  • TypeScript as a superset of JavaScript
  • Very basic intro to some TypeScript capabilities
  • Basic TypeScript compiler options
  • Import basic local class
  • Installing TypeScript type definitions
    • Import the ArcGIS API for JS (AMD & CDN)
  • Type definitions and code autocompletion
  • Load Execute code client-side
  • Migrate a sample app
  • Benefit from using VS code snippets
  • TS resources to keep learning
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment