1

My goal:

Build an AngularJS service (MapService) which initializes (MapService.initMap()) a 3rd party control (Esri ArcGIS Map) and return a reference to my map (MapService.getMap()) so I can use it in all my controllers.

Code

<!DOCTYPE html>
<html ng-app="app">   
  <body ng-controller="MapController">


      <script data-require="angular.js@1.5.0" data-semver="1.5.0" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.js"></script>
      <script defer type="text/javascript" src="//js.arcgis.com/4.0beta3/"></script>

      <script src="angular-esri-loader.js"></script>
      <script src="script.js"></script>
  </body>

</html>

Script.js:

(function(angular) {
    'use strict';

var app = angular.module('app', ['esri.core']);

angular.module('app')
    .controller('MapController', function(esriLoader, $scope, MapService) {

        MapService.getMap().then(function (mapObj) {
            console.log("resolved!!!"); // this code is never reached
            alert('show map');
            $scope.map = mapObj;
        });

        MapService.initMap();

    });



angular.module('app')
    .service('MapService',  function($q, esriLoader) {
        this.deferred = $q.defer();

        this.getMap = function(){
            return this.deferred.promise;
        }
        var self = this;
        this.initMap = function() {

         // self.deferred.resolve('test'); // When I uncomment this line, the promise will be successfully resolved and the 'then' runs

            esriLoader.require([
                    'esri/Map',
                    'esri/widgets/Search',
                    'esri/widgets/Search/SearchViewModel'
                ]).then(function(esriModules){
                    var Map = esriModules[0];
                    var Search = esriModules[1];
                    var SearchViewModel = esriModules[2];

                    // Create the map
                    var map = new Map({
                        basemap: 'satellite'
                    });

                    console.log("resolve map"); // this code is reached, however, it still doesn't seem to correctly resolve the promise

                    self.deferred.resolve(map);

                });

        }

    });
})(angular);

Plunkr

https://plnkr.co/edit/LEhYqHVSpscTZ5kJCfyJ

The problem:

The promise which getMap() returns is never resolved, so the code within MapService.getMap().then() is never reached.

When I resolve the promise with some test string (self.deferred.resolve('test');) in my initMap() method, it works.

So I suppose it's a problem of the 'esriLoader.require()' method which is another promise resolved after requireing some dependencies of the map component.

So I'm basically resolving a promise within a promise. I added the code of the esriLoader to plunkr.

What am I doing wrong?

user66875
  • 2,772
  • 3
  • 29
  • 55
  • Use one method `initMap` that will check if map is initialized then it will return map instance, otherwise it will initialize and then return instance. This will not solve problem. Just an advice for nice code. – karaxuna Feb 23 '16 at 08:03
  • 1
    Given that `esriLoader.require` already returns a promise, you should [avoid the deferred antipattern](http://stackoverflow.com/q/23803743/1048572)! – Bergi Feb 23 '16 at 08:14

2 Answers2

1

The problem is that Map implements some kind of promise interface. I recommend to check the documentation. You can solve your problem by wrapping the map into an object:

var map = {
  map: new Map({
    basemap: 'satellite'
  })
};

console.log("resolve map"); // this code is reached

self.deferred.resolve(map);

As others have already suggested it would be preferable to call MapService.initMap().then instead of calling both methods and creating your own promise. Instead of resolving that with the map you return the (wrapped) map:

        this.initMap = function() {

      //self.deferred.resolve('test'); // When I uncomment this line, the promise will be successfully resolved and the 'then' runs

        return esriLoader.require([
                'esri/Map',
                'esri/widgets/Search',
                'esri/widgets/Search/SearchViewModel'
            ]).then(function(esriModules){
                var Map = esriModules[0];
                var Search = esriModules[1];
                var SearchViewModel = esriModules[2];

                // Create the map
                var map = new Map({
                    basemap: 'satellite'
                });

                console.log("resolve map"); // this code is reached

                return {map: map};


            });
a better oliver
  • 26,330
  • 2
  • 58
  • 66
  • Thank you, this does work great! Just one more question: Right now, every `initMap` call creates a new Map. I would like to create it only once and each additional `MapService.initMap().then()` call would return a reference to the previously created map. How would you do that? – user66875 Feb 23 '16 at 10:45
-2

Try this way:

angular.module('app')
.service('MapService', function($q, esriLoader) {
    var map;
    this.initMap = function() {
        // return map instance if it's already initialized
        if (map) {
            return $q.resolve(map);
        }

        var defered = $q.defer();

        esriLoader.require([
            'esri/Map',
            'esri/widgets/Search',
            'esri/widgets/Search/SearchViewModel'
        ]).then(function(esriModules){
            var Map = esriModules[0];
            var Search = esriModules[1];
            var SearchViewModel = esriModules[2];

            // Create the map
            var map_ = new Map({
                basemap: 'satellite'
            });

            map = map_;
            defered.resolve(map_);
        });

        return defered.promise;
    }

});

Just use initMap when you need map instance :)

MapService.initMap().then(function (map) {
    // use map here
});
karaxuna
  • 26,752
  • 13
  • 82
  • 117
  • 1
    http://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it – Benjamin Gruenbaum Feb 23 '16 at 08:47
  • @BenjaminGruenbaum I don't know what kind of promise does `esriLoader.require` return. What if it returns es6 promise? Will it work with `$q`? – karaxuna Feb 23 '16 at 09:31