使用 angularjs 扩展 leaflet 的控件

leaflet提供了扩展控件的方法,可以自定义control,但现在想在自定义控件中使用angular的模板(templateUrl)和控制器(controller)机制绑定控件的属性和行为,类似于这种ui-router中定义路由或ui-bootstrap中定义弹出模态窗口的方式:

this.$modal.open({
    templateUrl: 'modules/acc/components/gis/equipmentFilter.html',
    controller: 'modules/acc/components/gis/equipmentFilter',
    controllerAs: 'vm'
});

想达到的效果是,按钮的 ng-click 能绑定 controller 的方法以及能使用 ng-model 绑定属性,而且是以 leaflet 扩展控件的方式加到地图中

点击控件按钮,执行方法弹出一个窗口

用到的技术点和具体思路如下:

  1. 定义好一个对象,包含templateUrl、template、controller、controllerAs…等相关属性
  2. 使用 $http 服务获取 templateUrl 定义的模板,或直接使用template定义的模板
  3. 使用 $rootScope 和 $controller 服务处理 controller 以及创建 scope 对象
  4. 使用 $compile 服务编译模板获取的模板,传递 scope
  5. 使用 L.DomUtil.create 创建一个用于 leaflet 的 Dom 元素
  6. 将编译好的模板对象加到 Dom 元素中
  7. 在 leaflet 自定义控件 L.Control.extend 时使用该元素

这里使用typescript实现,先定义一个 factory 返回一个创建 leaflet 控件的方法,具体代码如下:

import mod = require('modules/acc/module');
import angular = require('angular');
import L = require('leaflet');

function factory(
  $compile: ng.ICompileService,
  $rootScope: ng.IRootScopeService,
  $controller,
  $q,
  $http,
  $templateCache,
  $injector
) {
  function getTemplatePromise(options) {
    return options.template
      ? $q.when(options.template)
      : $http
          .get(
            angular.isFunction(options.templateUrl)
              ? options.templateUrl()
              : options.templateUrl,
            { cache: $templateCache }
          )
          .then(function(result) {
            return result.data;
          });
  }

  function getResolvePromises(resolves) {
    var promisesArr = [];
    angular.forEach(resolves, value => {
      if (angular.isFunction(value) || angular.isArray(value)) {
        promisesArr.push($q.when($injector.invoke(value)));
      }
    });
    return promisesArr;
  }

  return (controlOptions: acc.IMapControlOptions) => {
    var controlElm = L.DomUtil.create('div', 'leaflet-control-clegend');

    var templateAndResolvePromise = $q.all(
      [getTemplatePromise(controlOptions)].concat(
        getResolvePromises(controlOptions.resolve)
      )
    );

    templateAndResolvePromise.then(tplAndVars => {
      var controlScope = (controlOptions.scope || $rootScope).$new();

      //
      if (controlOptions.controller) {
        var ctrlInstance,
          ctrlLocals = {
            $scope: controlScope
          };
        var resolveIter = 1;

        angular.forEach(controlOptions.resolve, (value, key) => {
          ctrlLocals[key] = tplAndVars[resolveIter++];
        });

        ctrlInstance = $controller(controlOptions.controller, ctrlLocals);
        if (controlOptions.controllerAs) {
          controlScope[controlOptions.controllerAs] = ctrlInstance;
        }
      }

      // 定义一个控件最外层元素
      var angularDomEl = $(angular.element('<div></div>'));

      // 将元素添加到 leaflet 的 Dom 元素里,可能不定义最外层元素也行
      angularDomEl.attr('template-url', controlOptions.templateUrl);
      angularDomEl.html(tplAndVars[0]);
      controlElm.appendChild(angularDomEl.get(0));

      // 传递scope,编译整个新加入的元素
      $compile(controlElm)(controlScope);
    });

    // 这里是 leaflet 扩展控件的定义
    var t = L.Control.extend({
      options: {
        position: 'topright'
      },
      initialize: function(options) {
        L.Util.extend(this.options, options);
      },
      onAdd: function(map: L.Map) {
        // 最后元素要加到控件的 _container 中
        this._container = controlElm;

        return this._container;
      }
    });

    // 将 control 对象返回,可加到地图中
    return new t(controlOptions.options);
  };
}

factory.$inject = [
  '$compile',
  '$rootScope',
  '$controller',
  '$q',
  '$http',
  '$templateCache',
  '$injector'
];

mod.factory('modules/acc/factories/mapControl', factory);

实现一个模板

<div>
    <div class="btn-group leaflet-bar dropdown">
        <button class="btn btn-primary" ng-click="vm.test()">
            <i class="glyphicon glyphicon-cog"></i>
        </button>
        <button class="btn btn-default" ng-click="vm.test()">
            <i class="glyphicon glyphicon-search"></i>
        </button>
        <ul class="dropdown-menu dropdown-menu-right">
            <li>
                <a href="">显示1</a>
            </li>
            <li>
                <a href="">显示1</a>
            </li>
            <li>
                <a href="">显示1</a>
            </li>
        </ul>
        <button class="btn btn-success" data-toggle="dropdown">
            <i class="glyphicon glyphicon-road"></i>
        </button>
    </div>
    <div class="btn-group leaflet-bar">
        <button class="btn btn-default" ng-click="vm.test()">
            <i class="glyphicon glyphicon-refresh"></i>
        </button>
        <button class="btn btn-warning" ng-click="vm.test()">
            <i class="glyphicon glyphicon-volume-up"></i>
        </button>
        <button class="btn btn-default" ng-click="vm.eqFilter()">
            <i class="glyphicon glyphicon-filter"></i>
        </button>
    </div>
</div>

在加载 leaflet 的地方使用这个 factory,并将对象加到地图中

import mod = require('modules/acc/module');
import { MapBuilder } from 'modules/acc/extend/leaflet/mapBuilder';

class Controller {
  static $inject = ['$scope', '$element', 'modules/acc/factories/mapControl'];
  constructor(
    private $scope,
    private $element: JQLite,
    private mapControl: acc.IMapLayerFactory // 这里是刚才定义的 factory
  ) {
    $scope.vm = this;

    // 这个是另外定义的地图控件类,实际上返回的就是一个 leaflet 地图对象
    $scope.map = new MapBuilder(
      $($element)
        .find('.map-area')
        .get(0)
    ).map();

    // 这里创建控件,并加到地图中
    mapControl({
      templateUrl: 'modules/acc/views/gisTools.html',
      controller: 'modules/acc/controllers/gisTools',
      controllerAs: 'vm'
    }).addTo($scope.map);
  }
}

mod.controller('modules/acc/components/gis/index', Controller);

具体 controller 里的定义就是最常规的方法,没什么可说的

转载自:https://blog.csdn.net/weixin_38687913/article/details/81102479

You may also like...