Esri / esri-loader

A tiny library to help load ArcGIS API for JavaScript modules in non-Dojo applications
Apache License 2.0
458 stars 79 forks source link

Extend an esri class #127

Closed nickcam closed 6 years ago

nickcam commented 6 years ago

Hi Guys,

Any ideas on how to extend a class? Previously using imports I would just do the following for non-accessor and accessor classes.


import ScaleBarViewModel from "esri/widgets/ScaleBar/ScaleBarViewModel";
import SimpleRenderer from "esri/renderers/SimpleRenderer";
import asd from "esri/core/accessorSupport/decorators";

export class ScaleBarViewModelExtended extends ScaleBarViewModel {
   // implementation
}

@asd.subclass("FixRenderer")
export class FixRenderer extends asd.declared(SimpleRenderer) {
  // implementation
}

I know I can create the extended class outside of the project and then import the js in using dojo config, but there's some cases where having the extended class inside of the project is definitely preferable if not necessary (shared interfaces, project specific logic etc..) I'm using Angular 6.1.3 if that makes a difference.

Thanks.

tomwayson commented 6 years ago

Are you using the following set up to get the 4.x types?

Seems like it would then be:

export class ScaleBarViewModelExtended extends __esri.ScaleBarViewModel {
   // implementation
}

or some such nonsense.

nickcam commented 6 years ago

Hey Tom, Yep using the typings (added to tsconfig files as well)...and that's the first thing I tried thinking it may work 😕.

Get this error on the class definition line every time at __esri.ScaleBarViewModel. __esri is not defined.

Figured it must be because extends requires a class and not a type that is an interface like the typings export, but that might be wrong??

tomwayson commented 6 years ago

I think you're onto the right path there. Looks like [classes can implement interfaces, but not extend them](https://www.typescriptlang.org/play/#src=interface%20HasGreeting%20%7B%20%0D%0A%20%20%20%20greeting%3A%20string%3B%0D%0A%7D%0D%0Aclass%20Greeter%20extends%20HasGreeting%20%7B%0D%0A%20%20%20%20constructor(message%3A%20string)%20%7B%0D%0A%20%20%20%20%20%20%20%20this.greeting%20%3D%20message%3B%0D%0A%20%20%20%20%7D%0D%0A%20%20%20%20greet()%20%7B%0D%0A%20%20%20%20%20%20%20%20return%20%22Hello%2C%20%22%20%2B%20this.greeting%3B%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A%0D%0Alet%20greeter%20%3D%20new%20Greeter(%22world%22)%3B%0D%0A%0D%0Alet%20button%20%3D%20document.createElement('button')%3B%0D%0Abutton.textContent%20%3D%20%22Say%20Hello%22%3B%0D%0Abutton.onclick%20%3D%20function()%20%7B%0D%0A%20%20%20%20alert(greeter.greet())%3B%0D%0A%7D%0D%0A%0D%0Adocument.body.appendChild(button)%3B), and I've never used the __esri namespace types and did not realize that they export interfaces.

nickcam commented 6 years ago

Yeah just did a touch of reading and trialling as well and that seems to be the case. You wouldn't have any other ideas would you, everything I've tried is a dead end? This is a bit of deal breaker for me. Will seem to always need the class available at the import level to extend.

Do you know when you plan on moving to dojo 2? I haven't looked at it explicitly but I assume that it will use a more standard (if there is such a thing) loading pattern than dojo.

nickcam commented 6 years ago

I've got a workaround for this. Just had to dynamically import the extended classes modules when using them. This is to make sure they're not attempted to be loaded before the esri modules have been loaded.

I guess this keeps the extended classes consistent with the same async principle esri-loader employs.

import { EsriService } from "../esri.service";

// get the previously loaded esri modules required. Replace import statements.
let ScaleBarViewModel = EsriService.getModule<__esri.ScaleBarViewModelConstructor>("ScaleBarViewModel");
let asd = EsriService.getModule<__esri.decorators>("decorators");
let SimpleRenderer = EsriService.getModule<__esri.SimpleRendererConstructor>("SimpleRenderer");

@asd.subclass("FixRenderer")
export class FixRenderer extends asd.declared(SimpleRenderer) {
  // implementation
}

export class ScaleBarViewModelExtended extends ScaleBarViewModel {
  // implementation
}

To use them somewhere else.

let extended = await import("./esriextend/things");
scalebar.viewModel = new extended.ScaleBarViewModelExtended({ view: this.mapView });
let fr = new extended.FixRenderer();
YuukiLover commented 6 years ago

Hi,

I have the same issue to extend GraphicsLayer class inside a TypeScript class. I use esri-loader for esri class loading with Angular 4.

I saw you used a service called EsriService and was wondering if you could share some utilities of this service so I can fix my issue, or if you have any other way to extend a Dojo class in a Typescript class (I cannot use SystemJS in my project, EsriLoader is the only thing I can use)!

nickcam commented 6 years ago

Hi @YuukiLover, EsriService is just an angular service I've got to wrap the loading and storing of the esri modules from esri-loader.

It stores the loaded modules in a static dictionary so they can be referenced when extending classes, which is what is being retrieved in the EsriService.getModule<__esri.ScaleBarViewModelConstructor>("ScaleBarViewModel"); call.

There's a gist of it here https://gist.github.com/nickcam/162f3a375206737e5b5e42fc638c0fd8.

Hopefully it helps you out. I'm sure there's better ways to do this...but so far it's working out well enough for me.

nickcam commented 5 years ago

In case anyone else comes across this - I've had to change how I subclass esri modules as I found out the above method won't work when building using aot.

Now wrap the subclass in an outer class and expose it through a function call. This get's around the need for the await import() call (which wasn't working in aot), but still allows the required modules to be loaded at runtime.

import { EsriService } from "../services/esri.service";

// a class to hold and return the new type
export class ScaleBarExtendedType {

  // a method to return the new type
  public static getClass() {

    // dynamically get the required esri modules
    let scaleBar = EsriService.getModule<__esri.ScaleBarConstructor>("ScaleBar");

   // this is the declaration and the implementation of the subclass.
    let ScaleBarExtended = class extends scaleBar {

     // subclass implementation, constructor, overriden methods, properties etc...
      constructor(properties: __esri.ScaleBarProperties) {
        super(properties);
      }

      private _renderLine(a, b) {
         // override method implmentation
      };

   // return the subclass type
    return ScaleBarExtended;
  }
}

To use the subclass


// call the static method on the outer class to get the subclass type
let ScaleBarExtended = ScaleBarExtendedType.getClass();

// Instantiate it. Will still be statically typed as well so you'll get intellisense and all that goodness.
let scalebar = new ScaleBarExtended({ view: this.mapView, unit: "dual", id: "scalebar" });
tomwayson commented 5 years ago

Thanks for sharing the update @nickcam!

Cuberice commented 5 years ago

I created a Stackblitz for anyone wanting to see how to extend the TileLayer using the solution provided. Extending WebTileLayer

For my case we needed to override the getTileUrl method, to reverse the row. In order for this to work just supply a valid layer urlTemplate.

andygup commented 5 years ago

@Cuberice sweet! Just a minor comment and a question. A nice-to-have feature would be to tell the service at initialization time if you want to override the default ArcGIS JS API version in comparison to the hard coded override, for example something like this:

   this._esriService.init({ 
     url: 'https://js.arcgis.com/4.8/'
  })

I was also curious why you are using JS API 4.8? There's been a ton of improvements and new features since then. 😄

Cuberice commented 5 years ago

Hi

Yes good idea. In terms of the Esri version, for our production system we have been stuck on v4.6 due to a Bug in the Esri Drawing tools, where the Circle did not support our Spacial Reference. One of the reason we are also moving over to a TMS layer, hence this test, so we are now upgrading to v4.11 and sketch viewmodel.

On Thu, May 16, 2019 at 5:05 PM Andy notifications@github.com wrote:

@Cuberice https://github.com/Cuberice sweet! Just a minor comment and a question. A nice-to-have feature would be to tell the service at initialization time if you want to override the default ArcGIS JS API version in comparison to the hard coded override, for example something like this:

this._esriService.init({

 url: 'https://js.arcgis.com/4.8/'

})

I was also curious why you are using JS API 4.8? There's been a ton of improvements and new features since then. 😄

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Esri/esri-loader/issues/127?email_source=notifications&email_token=ABUCEFYHI6OF3MX3W4DPE23PVVZ3FA5CNFSM4FQMYDNKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODVSDJQQ#issuecomment-493106370, or mute the thread https://github.com/notifications/unsubscribe-auth/ABUCEFZN6M4DAXRCCGTF2WDPVVZ3FANCNFSM4FQMYDNA .

-- Neil de Swardt