Skip to content
This repository has been archived by the owner on Dec 1, 2020. It is now read-only.

help creating a factory that uses esriLoader #284

Closed
MarcBate opened this issue Jun 9, 2016 · 9 comments
Closed

help creating a factory that uses esriLoader #284

MarcBate opened this issue Jun 9, 2016 · 9 comments
Assignees
Labels

Comments

@MarcBate
Copy link

MarcBate commented Jun 9, 2016

I'd like to put my functions that use the ArcGIS JSAPI in an angular factory whose functions can be called from my controller or other factories. I'm running into problem since all the functions need to be within the esriLoader.require. Here's an example, where I'd like to be able to call mapFactory.loadBasemap("streets") from my controller.

module.factory('mapFactory', ['esriLoader',
    function (esriLoader) {
        var mapFactory = {};

        esriLoader.require([
            "esri/config",
            "esri/basemaps",
            "esri/map",
            "esri/graphic",
            "esri/Color",
        ], function (esriConfig, esriBasemaps, Map, Graphic, Color) {
            var map;

            mapFactory.loadBasemap = function (basemap) {
                map = new Map("mapDiv", {
                    basemap : basemap
                });
            };

            mapFactory.zoom = function (level) {
                map.setZoom(level);
            }
        });

        return mapFactory;
    }]);
@jwasilgeo jwasilgeo self-assigned this Jun 9, 2016
@jmarc-roy
Copy link

jmarc-roy commented Jun 10, 2016

Hi Marc Bate,
I use factories with angular-esri-map to have custom service and I do it this way :

app.factory("mapService", function(esriLoader, $q) {
  return {
    function1: function(map, url) {
      esriLoader.require(["esri/layers/FeatureLayer"], function(FeatureLayer) {
        var layer = new FeatureLayer({
          url: url
        })
        map.add(layer)
      })
    },
    function2: function(basemap) {
      esriLoader.require(["esri/Map", "esri/views/SceneView"], function(Map, SceneView) {
        var map = new Map({
          basemap: basemap
        })
        var view = new SceneView({
          container: "viewDiv"
        })
      })
    }
  }
})

You can see that for each function of my factory I use the esriLoader to load the module I need.
you can also use the service $q (promises) to chain your services :

mapService.function2("streets").then(function(map) {
  mapService.function1(map, "www.customUrl")
})

I hope it can help you.

@jwasilgeo
Copy link
Contributor

jwasilgeo commented Jun 10, 2016

@MarcBate thanks for posting this question, I think it is a good discussion to have. And thank you, @jmrroy for providing a good answer! (I took the liberty of editing both of your comments, just to make the js syntax highlighting appear; hope that's OK.)

I was going to suggest something similar to @jmrroy's suggest, since the esriLoader.require(... uses the Dojo require AMD loader and should not make a fresh request to fetch a module that it already has available. Thus, it should be fine to set up your factory's methods in this way. And, then you are only pulling in modules on-demand when they are actually needed by other directives/controllers that use this factory's logic.

If you are working on a single-map (in JSAPI v3) or perhaps a single-view (in JSAPI v4) application, you can also have your factory use and manage a $q deferred to let its methods appropriately work with a loaded and ready map (or view). You can also think of this as making your own version of this repo's esriRegistry service.

Using JSAPI v3, here is a rough example just for illustration:

app.factory('mapFactory', function(esriLoader, $q) {
  var mapFactory = {};

  mapFactory.mapDeferred = $q.defer();

  mapFactory.getLoadedMap = function() {
    // return the promise here, for use in other directives/controllers
    return mapFactory.mapDeferred.promise;
  };

  mapFactory.createMap = function() {
    esriLoader.require(['esri/map'], function(Map) {
      var map = new Map({
          basemap: 'dark-gray'
      });
      map.on('load', function() {
        // the Esri JSAPI v3 map load event has fired,
        // and now we can resolve the deferred with the map
        mapFactory.mapDeferred.resolve(map);
      };
    };
  };

  return mapFactory;
})

Then, in another directive/controller, you can inject this factory and do something like:

MapFactory.getLoadedMap().then(function(map) {
  // do whatever is needed with the map
});

Of course you can extend this idea and require more modules into your map factory and resolve them in deferreds, just like the single-map example above.

@tomwayson and I were considering documenting this paradigm in the our "Patterns" pages. If that seems valuable to capture these kinds of ideas, we could look into doing that. Let us know what you think!

@MarcBate
Copy link
Author

MarcBate commented Jun 10, 2016

Thanks for the help! I think it would be valuable to document in the Patterns pages.
I have ~200 functions and wonder if there's a way to avoid having to put the esriLoader.require in each individual function and load all of the ones I need just in once place in the factory.

@jwasilgeo
Copy link
Contributor

@MarcBate thanks for the feedback; in that case we'll likely add this topic to the Patterns pages. And sure, you could load what you need with esriLoader.require and resolve your own factory's deferred when the modules are loaded, passing them into the deferred's resolve (just as in the stripped down example above that only loads the map and resolves it as the only thing in the deferred).

@jwasilgeo
Copy link
Contributor

PS: Please see slide 69 of this recent presentation regarding this factory pattern: http://proceedings.esri.com/library/userconf/devsummit16/papers/dev_int_193.pdf

@jmarc-roy
Copy link

Hi all,
Good stuff to add this to the doc!
Since I move my app to angular-esri-map-2 using the arcgis API JS 4.0, I face some problem by resolving feature layer that I construct with source in a factory. I am unable to resolve the object and got this error in my console :

"init.js:160 RangeError: Maximum call stack size exceeded(…) "RangeError: Maximum call stack size exceeded
    at Deferred.extend.$$resolve "

I am able to resolve my layer by using angular.toJson(featureLayer) before, but when I reconsctruct the object with angular.fromJson(featureLayer) in my controller, many properties disappeared.

Do you have any Idea to resolve this problem?

Thanks in adavance for your help!

@jwasilgeo
Copy link
Contributor

@jmrroy we should have a new patterns page about the factory style pretty soon. You can follow along in PR #293. In your specific case while trying out angular-esri-map v2 and ArcGIS API for JavaScript v4, can you please provide the code in a live preview so that we can try to help you debug it?

@jmarc-roy
Copy link

Hi @jwasilgeo,
Sorry for the late response, I have actually no more problem. I fix my problem by resolving the result of "layerview-create" event when I add a new layer, and got no more problems to resolve with $q this object constructed in my factory.

@jwasilgeo
Copy link
Contributor

@MarcBate @GeoAstronaute we have a new patterns page up for the angular-esri-map v2 (JSAPI 4.x) documentation about custom factories. Check this out: http://esri.github.io/angular-esri-map/#/patterns/create-your-own-factories

I'll eventually make a very similar patterns page for angular-esri-map v1 (JSAPI 3.x), but there really won't be anything different other than the inherent differences between JSAPI 3.x and 4.x.

@MarcBate let's close this issue for now, but feel free to re-open if you need to.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants