Search code examples
javascriptangularjsdojoarcgisarcgis-js-api

AngularJS + ArcGIS


I am trying to use ArcGIS JavaScript API inside an Angular application. As I see, it uses Dojo. So, I am trying to initialize ArcGIS from Angular directive like this:

  link: function (scope, element, attrs) {
    dojo.require('esri.map');
    var init = function () {
      console.log('dojo is ready');
      var map = new esri.Map("map-container", {
        center: [-111.3797, 56.7266 ],
        zoom: 16,
        basemap: "streets"
      });
      map.enableScrollWheelZoom()

    };
    dojo.addOnLoad(init);
  }

Looks like this way is not 100% correct because when I try to zoom by scrolling mouse wheel, I get this error:

Uncaught TypeError: Cannot call method 'apply' of null 

My question, is how to properly to inject ArcGIS functionality inside an Angular app?


Solution

  • I think a very 'AngularJS' style approach to this would be something like the following. (fiddle here http://jsfiddle.net/technicolorenvy/2Ke62/4/)

    I like using angular-ui-router, but this approach would also work w/ Angular's $routeProvider. The magic here is in the resolve object which will optionally 'wait' until a promise is resolved before continuing.

    angular.module('webApp', ['ui.router'])
        // module (app) config
        .config(function ($stateProvider, $urlRouterProvider) {
    
            $urlRouterProvider.otherwise('/map');
    
            $stateProvider.state('map', {
                url: '/map',
                template: '<div id="map"></div>',
                controller: 'MapCtrl',
                resolve: {
                    promiseObj: function ($q, $rootScope, wish) {
                        var deferred = $q.defer(),
                            deps = {
                                Map: 'esri/map',
                                FeatureLayer: 'esri/layers/FeatureLayer',
                                InfoTemplate: 'esri/InfoTemplate',
                                SimpleFillSymbol: 'esri/symbols/SimpleFillSymbol',
                                SimpleRenderer: 'esri/renderers/SimpleRenderer',
                                SimpleMarkerSymbol: 'esri/symbols/SimpleMarkerSymbol',
                                ScaleDependentRenderer: 'esri/renderers/ScaleDependentRenderer',
                                Color: 'dojo/_base/Color'
                            };
    
                        wish.loadDependencies(deps, function () {
                            deferred.resolve();
                            if (!$rootScope.$$phase) {
                                $rootScope.$apply();
                            }
                        });
    
                        return deferred.promise;
                    }
                }
            });
        });
    

    As you can see above, we have a map state that has a resolve prop. You can then build an object that represents your ArcGIS/Dojo dependencies and pass that on to your wish.loadDependencies (see below).

    Using q we will return a promise that gets resolved once all the dependencies are loaded via dojo's require

    angular.module('webApp')
         // service that deals w/ our dojo require
        .service('wish', function () {
    
            // it's not require... it's a wish?
            var wish = {};
    
            function _loadDependencies(deps, next) {
                var reqArr = _.values(deps),
                    keysArr = _.keys(deps);
    
                // use the dojo require (required by arcgis + dojo) && save refs
                // to required obs
                require(reqArr, function () {
                    var args = arguments;
    
                    _.each(keysArr, function (name, idx) {
                        wish[name] = args[idx];
                    });
    
                    next();
                });
            }
    
            return {
                loadDependencies: function (deps, next) {
                    _loadDependencies(deps, next);
                },
    
                get: function () {
                    return wish;
                }
            };
        });
    

    after that, in your MapCtrl, you can call all the ArcGIS/Dojo fns directly (as you would normally), by the keys you used in the deps object that was constructed during app config, as they are now attached to the object returned by wish.get().

    What follows is a modified version of the example found here (https://developers.arcgis.com/en/javascript/jssamples/renderer_proportional_scale_dependent.html)

    angular.module('webApp')
        // our map controller
        .controller('MapCtrl', function ($rootScope, $scope, wish) {
    
            var w = wish.get(),
                greenFill = new w.Color([133, 197, 133, 0.75]),
                greenOutline = new w.Color([133, 197, 133, 0.25]),
                layer,
                markerSym,
                renderer1,
                renderer2,
    
                CROPS_URL = 'http://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/USA_County_Crops_2007/FeatureServer/0';
    
            $scope.map = new w.Map('map', {
                center: [-98.579, 39.828],
                zoom: 4,
                basemap: 'gray'
            });
    
            layer = new w.FeatureLayer(CROPS_URL, {
                outFields: ['STATE', 'COUNTY', 'M086_07', 'AREA'],
                infoTemplate: new w.InfoTemplate('${COUNTY}, ${STATE}', '<div style="font: 18px Segoe UI">The percentage of the area of the county that represents farmland is <b>${M086_07}%</b>.</div>')
            });
            layer.setDefinitionExpression('AREA>0.01 and M086_07>0');
    
    
            markerSym = new w.SimpleMarkerSymbol();
            markerSym.setColor(greenFill);
            markerSym.setOutline(markerSym.outline.setColor(greenOutline));
            renderer1 = new w.SimpleRenderer(markerSym);
            renderer1.setProportionalSymbolInfo({
                field: 'M086_07',
                minSize: 1,
                maxSize: 10,
                minDataValue: 0,
                maxDataValue: 100
            });
    
            //for the second renderer increase the dot sizes and set a backgroundFillSymbol
            renderer2 = new w.SimpleRenderer(markerSym);
            renderer2.setProportionalSymbolInfo({
                field: 'M086_07',
                minSize: 5,
                maxSize: 15,
                minDataValue: 0,
                maxDataValue: 100
            });
    
            layer.setRenderer(new w.ScaleDependentRenderer({
                rendererInfos: [{
                    'renderer': renderer1,
                        'minScale': 50000000,
                        'maxScale': 10000000
                }, {
                    'renderer': renderer2,
                        'minScale': 0,
                        'maxScale': 5000000
                }]
            }));
    
            $scope.map.addLayer(layer);
        });
    

    working fiddle demonstrating the above code, found here http://jsfiddle.net/technicolorenvy/2Ke62/4/