Using widgets with AngularJS


Note: Support for 3D on mobile devices may vary, view the system requirements for more information.

This sample shows how to use AngularJS (version 1.x) with ViewModels to create a custom widget experience. Specifically, it demonstrates how to use the ZoomViewModel to create a custom zoom buttons directive.

Note, if you are starting a new project we recommend you consider using Angular (version 2+) with the esri-loader.

1. Set up AngularJS and angular-esri-map

First, you need to load the necessary libraries, including angular-esri-map, which is a collection of directives and services to help you use the ArcGIS API for JavaScript in your AngularJS applications.

<!-- load Esri JSAPI -->
<script src=""></script>
<!-- load AngularJS -->
<script src=""></script>
<!-- load angular-esri-map -->
<script src=""></script>

2. Create a simple map and AngularJS view directive

Create a simple map and add it to either a MapView or a SceneView with the help of angular-esri-map directives. If you are unfamiliar with views, how to create a basic map, or the directive and controller pattern provided by angular-esri-map, see the following resources:

<!-- this directive is provided by angular-esri-map -->
<esri-map-view map=""
    zoom: 10,
    center: [-100.33, 25.69],
    ui: {
      components: ['attribution']
    constraints: {
      minZoom: 7,
      maxZoom: 12
// use angular-esri-map to load Esri modules
// Note: this is not related to esri-loader:
], function(Map) {
  // create a Map,
  // which will become bound to <esri-map-view> by angular-esri-map = new Map({
    basemap: 'topo'

3. Create a custom AngularJS directive

Create a custom directive with isolated scope bindings, a template, and controller logic. For more information on this, see the AngularJS documentation. To display the MapView's or SceneView's current scale and zoom values, watch those view properties for changes and then manually apply scope for the directive. Next, bind the click actions of the directive to the methods of the ZoomViewModel. It is also possible to bind the directive's conditional CSS classes with the ZoomViewModel's properties when determining the current min/max zoom level.

.directive('zoomButtons', function zoomButtons() {
  return {
    // element only
    restrict: 'E',

    // isolate scope
    scope: {
      view: '=', // required: a MapView or SceneView instance
      viewUiPosition: '&' // optional: the layout position relative to the view UI

    template: [
      '  <div class="zoom-btns">',
      '    <div class="button circle raised"',
      '      ng-class="{ \'disable\': !zoomButtonsCtrl.zoomVM.canZoomIn }"',
      '      ng-click="zoomButtonsCtrl.zoomIn()">',
      '      <i class="material-icons">add</i>',
      '    </div>',
      '    <div class="button circle raised"',
      '      ng-class="{ \'disable\': !zoomButtonsCtrl.zoomVM.canZoomOut }"',
      '      ng-click="zoomButtonsCtrl.zoomOut()">',
      '      <i class="material-icons">remove</i>',
      '    </div>',
      '  </div>',
      '  <div class="view-info">Scale: {{ zoomButtonsCtrl.view.scale | number:1 }}</div>',
      '  <div class="view-info">Zoom: {{ zoomButtonsCtrl.view.zoom | number:2 }}</div>',

    controllerAs: 'zoomButtonsCtrl',

    bindToController: true,

    controller: function zoomButtonsController($element, $scope, esriLoader) {
      var self = this;

      // get reference to the directive's element
      var element = $element.children()[0];

      // for AngularJS v1.6+, put initialization logic inside `$onInit()`
      // to make sure bindings have been initialized
      this.$onInit = function() {
          // get reference to the directive's optional view UI position
          self.uiPosition = self.viewUiPosition();

      this.setView = function(view) {
        // the view binding has been changed
        // (see the directive's link method)
        if (!view) {

        // load and establish the Esri ZoomViewModel
        ], function(ZoomVM) {
          self.zoomVM = new ZoomVM({
            view: view

          // optional: set the UI position of this directive within the view
          if (self.uiPosition) {
            view.ui.add(element, self.uiPosition);

'scale,zoom', function() {
            // this is outside of the AngularJS digest cycle;
            // apply scope manually to update bindings in the template:
            //  - ng-class for the button divs styling
            //  - scale and zoom for the view info divs

      // an ng-click in the template is bound to this method
      this.zoomIn = function() {
        if (this.zoomVM.canZoomIn) {

      // an ng-click in the template is bound to this method
      this.zoomOut = function() {
        if (this.zoomVM.canZoomOut) {

    link: function zoomButtonsLink(scope, element, attrs, controller) {
      // the directive relies on a MapView or SceneView instance;
      // watch for the change to the view binding in the ExampleController
      scope.$watch('zoomButtonsCtrl.view', function(newVal) {

4. Render the custom AngularJS directive

Finally, add your custom directive to the DOM, giving it a reference to the view and optionally a UI layout position.

<zoom-buttons view="vm.mapView"
    position: 'bottom-left'

Sample search results