Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Easy to use service wrapper around esri-loader? #124

Closed
nickcam opened this issue Jul 25, 2018 · 5 comments
Closed

Easy to use service wrapper around esri-loader? #124

nickcam opened this issue Jul 25, 2018 · 5 comments
Labels
FAQ Frequently asked questions

Comments

@nickcam
Copy link

nickcam commented Jul 25, 2018

Hi Guys, not really an issue...but more a request for guidance.
I'm getting around to upgrading an angular app to cli 6.x and moving away from esri-system-js.
It's basically a map and some menus and has heaps of esri dependencies through out.

As such it would be a real pain to have to load modules using this pattern everywhere:

loadModules(['esri/views/MapView', 'esri/Map'])
.then(([MapView, Map]) => {
  // do stuff
});

Even using await it's not so nice to have scattered everywhere.
I've created a service to inject around that can encapsulate this and a list of modules I'll want to load straight away or lazily. They're in this gist - https://gist.github.com/nickcam/162f3a375206737e5b5e42fc638c0fd8

This will allow creating a new arcgis object like so where the service is injected:
let map = new this.esri.Map({ //whatever params// });

To lazy load:

await this.esri.load(["SimpleMarkerSymbol"]);
let sms = new this.esri.SimpleMarkerSymbol();

This seems ok-ish, the overhead is adding every module to the modules list and creating a property on the service for each module (for typings and ease of calling).

I can't help but feel it's a bit shit though...so before I crack on and rewrite a bunch of stuff wanted to check if you have any better ideas??
I have checked out the other issues in this repo - particularly this one, #71, which I think is similar, but not quite as succinct as I would have hoped.

Also any examples of using typescript 2.9 import() yet?
Thanks heaps!

@andygup
Copy link
Member

andygup commented Jul 25, 2018

Our plan is to start using arcgis-webpack-plugin when Angular CLI 6 provides official support for ng eject. This should make arcgis module loading signficantly easier/cleaner/efficient. It worked great with Angular 5.

https://github.com/Esri/arcgis-webpack-plugin

Currently it's anyone's guess as to when ng eject will be fixed. Last I checked, it wasn't included in v6.1.0-rc. Until then we are pretty much stuck with the patterns you mention above, unless you want to go with an unsupported eject pattern, some of which are discussed in this issue here: angular/angular-cli#10618

Also any examples of using typescript 2.9 import() yet?

I wish...still waiting to verify if Angular 6.1.0 will support support TS 2.8 and 2.9.

@nickcam
Copy link
Author

nickcam commented Jul 25, 2018

Ok fair enough, thanks @andygup! Will just use what I've got until things are updated.

@nickcam nickcam closed this as completed Jul 25, 2018
@tomwayson
Copy link
Member

tomwayson commented Jul 27, 2018

it would be a real pain to have to load modules using this pattern everywhere

Actually, I consider it a significant benefit of esri-loader that it encourages you to:

  1. consolidate your use of ArcGIS API modules in a single or few locations
  2. use only the ArcGIS API modules you need to do ArcGIS API things (map or 3D scene visualizations) and rely on your framework of choice, arcgis-rest-js, and/or modern JavaScript/TypeScript for everything else

The problem comes when you need to use something that really has no reason to be async (like a Graphic, SimpleMarkerSymbol, Extent, etc) in some other component that is not the map/scene component. In those situations, my first suggestion is to just create and pass around JSON as much as you can. Still, there are times when you need to create a new instance of one of those classes, and for those times I've suggested this solution #71 (comment) and that is working very well for us in the ArcGIS Hub. That is very similar to your idea above of using a service. One subtle but important difference is that I suggest your service expose functions instead of classes, and that those functions not return the underlying ArcGIS classes or instances if possible. That way, nothing outside your service needs to know about the idiosyncrasies of how those modules are loaded, or the __esri types, etc.

The key to success w/ that kind of pattern is to not overgeneralize, i.e. "adding every module to the modules list and creating a property on the service for each module (for typings and ease of calling)." Instead, focus on the specific workflows of your application. For example, let's say your app has a sidebar component that will need to at some point create a new SimpleMarkerSymbol, and you know that component is only visible once the map component has loaded. For this scenario, your service's createMap() can lazy-load SimpleMarkerSymbol along w/ whatever other modules it needs to show the map and then set this._SimpleMarkerSymbol so that the service's addSymbolToMap(symbolJson) will be available the sidebar component:

addSymbolToMap(symbolJson) {
  if (this._SimpleMarkerSymbol) {
    const symbol = new this._SimpleMarkerSymbol(symbolJson)
    // TODO: add the symbol to this._map, etc
  } else {
    // this should never happen, but just in case
    throw new Error('map not loaded yet')
  }
}

Sure, it would be a pain to create such a wrapping function for every module, but if you focus on your app's specific workflows, I bet you'll find that you should only need it for a few modules/classes.

Our plan is to start using arcgis-webpack-plugin when Angular CLI 6 provides official support for ng eject.

To clarify, that's @andygup's plan for https://github.com/Esri/angular-cli-esri-map - it's not "Esri's plan is to deprecate esri-loader once Angular get's it's act together."

I'll admit that being able to import any esri module from any component, etc is convenient, and it's great that the webpack plugin is going to allow that pattern. However, it comes at a hidden cost - you could end up bloating your bundles with many dependencies that aren't needed until later. esri-loader makes you think about when you load/use ArcGIS modules, and I think that's a good thing. You can achieve the same w/ the webpack plugin by using dynamic import(), but the difference w/ esri-loader is that lazy-loading is the default, and you have to think about sync, but w/ the plugin sync is the default (bundle bloat) and you have to think about lazy loading.

@nickcam
Copy link
Author

nickcam commented Jul 29, 2018

Hi @tomwayson - awesome, thanks for the detailed write up!
Being used to the ease of import statements and instantiating from them might have spoiled me...the arguments against them are compelling though.

Maybe this app is a bit of rare case, but I know that I'll need about 80% of the modules I want to use loaded up front just to get the initial view, the flexibility to lazy load when required is a cool thing to have up the sleeve though.

I thought about using void functions to expose/use the modules, and after looking at the example again I can see this app basically already does. Currently there's multiple services that relate to a particular app function or area each that import esri modules and they contain the sorts of functions you have in your example, so there's no esri module bleed into views or outside these services. "Theoretically" we could swap out arcigis api for something else...although in reality it's not practical without basically rewriting the app.

Some of these services are quite involved and could probably be refactored (hindsight, lessons learned etc..), what I was hoping for was easier instantiation within these services by wrapping the loader with module defs to sit behind them. Also have some classes that extend esri modules, haven't looked at refactoring those yet though so not sure how that will go??

Thanks for your help, plenty to consider as I go about refactoring this thing :)!

@tomwayson tomwayson added the FAQ Frequently asked questions label Jan 31, 2019
@tomwayson
Copy link
Member

Here's another variant on the pattern I suggested above.

In your fn that calls loadModules() load all classes that your app needs to work w/ the map. Then resolve the promise w/ one or more fns that close over the map/scene view and the classes. Callers can then use those functions to work w/ the map. Here's an example:

I have a newMap() fn that resolves with a refreshGraphics() fn after the map loads:

https://github.com/tomwayson/create-arcgis-app/blob/fefb171c2ebaaae90292e6ba981c3aff65b340b5/src/utils/map.js#L38-L41

Then in the component that calls newMap(), it holds on to refreshGraphics() for later use:

https://github.com/tomwayson/create-arcgis-app/blob/fefb171c2ebaaae90292e6ba981c3aff65b340b5/src/components/ExtentsMap.js#L30-L38

Whenever that component receives a new array of items (JSON), it calls refreshGraphics().

https://github.com/tomwayson/create-arcgis-app/blob/fefb171c2ebaaae90292e6ba981c3aff65b340b5/src/components/ExtentsMap.js#L12-L27

For good measure I delete the map component's refernece to refreshGraphics() before the component is destroyed:

https://github.com/tomwayson/create-arcgis-app/blob/fefb171c2ebaaae90292e6ba981c3aff65b340b5/src/components/ExtentsMap.js#L45-L49

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
FAQ Frequently asked questions
3 participants