Select to view content in your preferred language

Custom BaseTileLayer in Angular

1982
9
04-02-2021 12:53 AM
DaveAdams
Regular Contributor

Hi,

I'm trying to add a custom TileLayer to the map using the new ES modules in 4.18 but am unsure how to implement the createSubclass method on BaseTileLayer. I can see that is a dojo method so was unsure how to perform the equivalent in Typescript/Angular.

Any help would be greatly appreciated.

0 Kudos
9 Replies
AndyGup
Esri Regular Contributor

Here's an AMD example: https://developers.arcgis.com/javascript/latest/sample-code/sandbox/index.html?sample=layers-custom-.... There aren't any dojo methods in the 4.18 API. The way the modules functionality is implemented in ESM (POJO or TypeScript) is exactly the same as AMD, you just need to change the path used by the import statements: https://developers.arcgis.com/javascript/latest/es-modules/#migrate-to-es-modules

0 Kudos
DaveAdams
Regular Contributor

Hi Andy,

I believe I have imported correctly 

import BaseTileLayer from '@arcgis/core/layers/BaseTileLayer';
 
however I get an error on the createSubClass method where it says the property does not exist on type BaseTileLayerConstructor when following the sandbox example .
 
var imageLayer = BaseTileLayer.createSubClass({
      properties: {
        urlTemplate: null,
        tint: {
          value: null,
          type: Color
        }
      },

      // generate the tile url for a given level, row and column
      getTileUrl: function(levelrowcol) {
        return urlTemplate
          .replace("{z}"level)
          .replace("{x}"col)
          .replace("{y}"row);
      }

    });
 
 

 

 

0 Kudos
AndyGup
Esri Regular Contributor

I can repro, that's a TypeScript definition problem, I'll open an issue. You can temporarily bypass the issue and continue your builds by using //@ts-ignore:

 

        //@ts-ignore
        var ImageLayer = BaseTileLayer.createSubclass({

 

I also see that the API reference documentation needs to be updated. We're looking into that, as well. Thanks for reporting.

DaveAdams
Regular Contributor

Hi Andy, many thanks, that workaround works for now and my tile layer is now available in the map. I did find a similar issue in an older version here: https://community.esri.com/t5/arcgis-api-for-javascript/customelevationlayer-with-typescript/m-p/380...

 

0 Kudos
bearingwest
Emerging Contributor

@AndyGup has this still not been fixed? I'm running into this on 4.27. 

0 Kudos
AndyGup
Esri Regular Contributor

@bearingwestlooks like I never clarified my answer from above. If you are using TypeScript with '@arcgis/core', then the error "Property 'createSubclass' does not exist on type 'typeof SomeXYZLayer'" is correct when attempting to use the createSubclass() method. The createSubclass() method only works in JavaScript. We updated the documentation about subclassing in TypeScript here: https://developers.arcgis.com/javascript/latest/implementing-accessor/#create-a-simple-subclass.  

The correct pattern in TypeScript would look like the following. You might also need to set 'useDefineForClassFields:false' in your tsconfig, depending on your project configuration.

import { subclass } from "@arcgis/core/core/accessorSupport/decorators";

import BaseTileLayer from "@arcgis/core/layers/BaseTileLayer.js";

@subclass("custom.MyTileLayer")
export class MyTileLayer extends BaseTileLayer {
  // TODO
}

 

bearingwest
Emerging Contributor

Thanks, looks promising. However, when I do this, I get:

init.js:149 [esri.Map] #add() The item being added is not a Layer or a Promise that resolves to a Layer.

Whereas when I used ts-ignore and followed a js-ish pattern (as in https://community.esri.com/t5/arcgis-javascript-maps-sdk-questions/custom-layer-is-not-being-added-f... - which, notably, is not working completely) I can at least add the layer without error.

My source, following your pattern, follows. I suspect the namespacing might be a problem (custom...) but I've tried all permutations that make sense (classname by itself, fully qualified class name from src up, and everything in between) to no avail. What am I doing wrong here? (I'm also fine with the ts-ignore hack linked above, assuming I can get this working.)

(I don't suppose a dojo-less typescript version is on the cards, is it? The DX of the current version is quite difficult.)

@subclass("custom.MyCustomLayer")
export class MyCustomLayer extends BaseTileLayer {

    constructor(properties: BaseTileLayerProperties,
                private readonly myArg1: SomeClass,
                private readonly myArg2: SomeClass,
                private readonly myArg3: SomeClass) {
        super(properties);
    }

    getTileUrl(level: number, row: number, col: number): string {
        console.log("Getting tile URL", level, row, col);
        return `https://proxy.com/z=${level}&x=${col}&y=${row}`;
    }

    fetchTile(level: number, row: number, col: number): Promise<HTMLImageElement | HTMLCanvasElement> {
        console.log("Fetching tile", level, row, col);

        return new Promise<HTMLImageElement>((resolve, reject) => {

 

0 Kudos
AndyGup
Esri Regular Contributor

Please provide a minimal repro sample, there's more info on that here: https://developers.arcgis.com/javascript/latest/troubleshooting/#minimal-reproducible-application. One suggestion is to first build the custom layer in JavaScript, and then migrate your code to TypeScript. 

Did you try adding a load() method. There are several samples to use for guidance: https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-tilelayer/ , https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-blendlayer/ and https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-elevation-exaggerated/.

> I don't suppose a dojo-less typescript version is on the cards, is it? 

There hasn't been any dojo in the 4.x API in quite a while, and it was removed from the CDN in version 4.25.

bearingwest
Emerging Contributor

I can't provider a minimal repro sample. Like I said, this is an extension that sits inside another application.

One suggestion is to first build the custom layer in JavaScript, and then migrate your code to TypeScript. 

Well that speaks exactly to my question, doesn't it? I can't subclass - I have to use @subclass, which is a hack (and which doesn't work). I can't just import, I need to use loadModules, which is a hack. If it's not dojo, fine - but when will you produce a clean SDK that follows web standards, with regular types without all of these hacks throughout your system? There's no need for all of this "cleverness". 

Here's another delightful example; this previously working code results in:

Accessor#set Assigning an instance of 'esri.geometry.SpatialReference' which is not a subclass of 'esri.geometry.SpatialReference'

 

    addLine1(coords: CRTM05Coordinates[][], color: string, width: number): number {
        const line = new Polyline({
            paths: coords.map(ring => ring.map(point => [point.east, point.north])),
            spatialReference: this.w.view.spatialReference
        });
        return this.addLine2(line, color, width);
    }

(This is, btw, "fixed" by ` spatialReference: { wkid: this.w.view.spatialReference.wkid }`).

Yet another example:

this.graphicsLayer = new GraphicsLayer({
   title: "My graphics layer"
});

// Add the graphics layer to the map
this.w.map.add(this.graphicsLayer);

 

Results in:

#add() The item being added is not a Layer or a Promise that resolves to a Layer.

This is fixed by:

        loadModules(['esri/layers/GraphicsLayer']).then(([GraphicsLayerClass]) => {
            this.graphicsLayer = new (GraphicsLayerClass as typeof GraphicsLayer)({
                title: "My graphics"
            });

            // Add the graphics layer to the map
            this.w.map.add(this.graphicsLayer);
        });

 

...despite the underlying site already having initialized via:

        require([
...
            'esri/layers/GraphicsLayer',
0 Kudos