从 AngularJS 中的指令访问 $scope

Accessing $scope From a Directive in AngularJS

由于我们缺乏使用 AngularJS 进行开发的专业知识,我们在开发过程中遇到了另一个障碍。

我们正在开发一个 Angular/Web API 应用程序,其中我们的页面仅包含一个交互式 SVG 图,当用户将鼠标悬停在 Angular 指令中的特定 SVG 标记上时,该图会显示数据.

应用程序中当前有两个自定义指令。

  1. 指令一 - 将 SVG 文件加载到网页中
  2. 指令二 - 添加 SVG 元素悬停 event/data 过滤器

指令一:

//directive loads SVG into DOM
angular.module('FFPA').directive('svgFloorplan', ['$compile', function  ($compile) {
return {
    restrict: 'A',

    templateUrl: 'test.svg',
    link: function (scope, element, attrs) {

        var groups = element[0].querySelectorAll("g[id^='f3']")
        angular.forEach(groups, function (g,key) {
            var cubeElement = angular.element(g);
            //Wrap the cube DOM element as an Angular jqLite element.
            cubeElement.attr("cubehvr", "");
            $compile(cubeElement)(scope);
        })
    }
}
}]);

SVG 图包含具有唯一标识符的标签,即:

<g id="f3s362c12"></g>

指令从与每个 SVG 标签 ID 相对应的注入服务加载 JSON 数据。

 //filters json based on hover item
 dataService.getData().then(function(data) {
    thisData = data.filter(function (d) {
    return d.seatId.trim() === groupId
 });

如上所示,指令二还添加了悬停事件功能,可根据悬停的标签过滤 JSON 数据。

IE:如果用户将鼠标悬停在 上,指令中的过滤器将 return 这条 JSON 记录:

{"Id":1,
 "empNum":null,
 "fName":" Bun E.",
 "lName":"Carlos",
  ...
 "seatId":"f3s362c12 ",
 "floor":3,
 "section":"313 ",
 "seat":"12 "}

指令二:

//SVG hover directive/filter match json to svg
angular.module("FFPA").directive('cubehvr', ['$compile', 'dataService',     function ($compile, dataService) {
return {
    restrict: 'A',
    scope: true,
    link: function (scope, element, attrs) {

        //id of group 
        scope.elementId = element.attr("id");
        //alert(scope.elementId);
        var  thisData;
        //function call
        scope.cubeHover = function () {

            //groupId is the id of the element hovered over.
            var groupId = scope.elementId;

            //filters json based on hover item
            dataService.getData().then(function(data) {
            thisData = data.filter(function (d) {
                return d.seatId.trim() === groupId
            });
              //return data.seatId === groupId
              scope.gData = thisData[0];
              alert(thisData[0].fName + " " + thisData[0].lName + " " +   thisData[0].deptId);  
            });
            //after we get a match, we need to display a tooltip with   save/cancel buttons.
            $scope.empData = $scope.gData;
        };
        element.attr("ng-mouseover", "cubeHover()");
        element.removeAttr("cubehvr");
        $compile(element)(scope);
    }
    //,
    //controller: function($scope, $element){
    // $scope.empData = $scope.gData;
    //}
  }
}]);

我们现在面临的问题是(除了拥有最少的 Angular 经验和面临独特而困难的实施问题之外)我们正在尝试实施一种使用 div 标签和一个 angular 范围变量,当用户将鼠标悬停在 SVG 标签元素上时我们可以显示它(而不是下面的 Plunker POC link 中演示的 Javascript 警报)。

由于数据是由指令驱动的,并且指令已经将"cubehvr"作为参数:

angular.module("FFPA").directive('*cubehvr*', ['$compile', 'dataService', function ($compile, dataService)

我们被卡住了,因为我们不知道如何设置 HTML 页面范围指令或变量,从我们的第二个指令中这样说:

<div uib-popover="Last Name: {{empData.lName}}" 
    popover-trigger="'mouseenter'" 
    type="div" 
    class="btn btn-default">Tooltip
</div>

或者简单地说,这个:

  <div emp-info></div>

div 工具提示将有 html 按钮调用 Web API 更新功能。

我们这里有一个缩小版的 POC Plunk:

POC Plunk

还考虑使用 Angular Bootstrap UI 作为工具提示:

Bootstrap UI Plunk

希望这是有道理的。

//编辑。我再次阅读了您的问题并仔细阅读了我的答案。我没有完全回答你的问题,因为它 multi-layered。现在我将解决您所有的问题并尝试回答它们:

  1. 将 $scope 传递给其他指令。

$scope 在 MVVM 设计模式中是 Model-View,它将模板(视图)和模型粘合在一起。理论上你可以将 $scope 传递给另一个指令,但我认为它是一个 anti-pattern.

  1. directives.There 之间的通信至少有 4 种方法,您可以使用这些方法来传达您的指令:

    • 共享相同的范围,你几乎在你的 plunker 中做了什么,只是不要在你的指令规范中定义任何 'scope'。我不确定这是否是最好的方法,因为您的任何指令都会使您的示波器数据变形。
    • 创建隔离范围并使用ng-model 或$watch,这是更安全的方法,但需要更多开销。在这种情况下,您将变量传递给您的范围。$watch。是 two-way-binding。您可以推拉该值。 $watch
    • 创建一个服务,在其中保留诸如 event-bus 之类的东西或变量的存储空间
    • 您可以通过事件传达您的指令: $on $emit 这适用于分层指令(如此有效,您必须创建隔离的 child 范围)
  2. 将弹出窗口添加到 SVG 的 child。 Bootstrap 能够将弹出窗口添加到 body 而不是 parent 元素。它对 SVG 很有用:https://angular-ui.github.io/bootstrap/#!#popover

我重构了您的代码以使用两个指令,并将数据加载到控制器中。一个指令包装 popover,第二个指令传递数据,popover 现在也使用模板,所以它被编译为 angular:

  var app = angular.module('FFPA',  ['ngAnimate', 'ngSanitize', 'ui.bootstrap']);

  //controller
  app.controller('myCtrl', function ($scope, dataService) {
    $scope.test = 'test';
    dataService.getData().then(function(data) {
      $scope.dataset = data.reduce(function (obj, item) {
        obj[item.seatId.trim()] = item;
        item.fullName = item.fName + ' ' + item.lName;
        return obj;
      }, {});
    });
  });

  angular.module('FFPA').service('dataService', function($http){
    this.getData = function(){
      return $http.get("data.json").then(
        function(response){
          return response.data;
        }, function() {
          return {err:"could not get data"};
        }
      );
    }
  });

  //directive loads SVG into DOM
  angular.module('FFPA').directive('svgFloorplan', ['$compile', function ($compile) {
      return {
        restrict: 'A',
        templateUrl: 'test.svg',
        scope: {
          'dataset': '=svgFloorplan'
        },
        link: {
          pre: function (scope, element, attrs) {
            var groups = element[0].querySelectorAll("g[id^='f3']");
            scope.changeName = function (groupId) {
              if (scope.dataset[groupId] && scope.dataset[groupId].lastName.indexOf('changed') === -1) {
                scope.dataset[groupId].lastName += ' changed';
              }
            }

            groups.forEach(function(group) {
              var groupId = group.getAttribute('id');
              if (groupId) {
                var datasetBinding = "dataset['" + groupId + "']";
                group.setAttribute('svg-floorplan-popover', datasetBinding);

                $compile(group)(scope);
              }
            });
          }
        }
      }
  }]);

  angular.module('FFPA').directive('svgFloorplanPopover', ['$compile', function ($compile) {
      return {
        restrict: 'A',
        scope: {
          'person': '=svgFloorplanPopover'
        },
        link: function (scope, element, attrs) {
          scope.changeName = function () {
            if (scope.person && scope.person.fullName.indexOf('changed') === -1) {
              scope.person.fullName += ' changed';
            }
          }
          scope.htmlPopover = 'popoverTemplate.html';
          element[0].setAttribute('uib-popover-template', "htmlPopover");
          element[0].setAttribute('popover-append-to-body', 'true');
          element[0].setAttribute('popover-trigger', "'outsideClick'");
          element[0].querySelector('text').textContent += '{{ person.fullName }}';

          element[0].removeAttribute('svg-floorplan-popover');

          $compile(element)(scope);

        }
      }
  }]);

你的 HTML body 现在看起来像:

  <body style="background-color:#5A8BC8;">
    <div ng-app="FFPA" ng-controller="myCtrl">
      <div svg-floorplan="dataset"></div>
    </div>
  </body>

HTML 用于弹出窗口:

  <div><button type="button" class="btn btn-default" ng-click="changeName()">{{ person.fullName }}</button></div>

这是工作中的 plunker: http://plnkr.co/edit/uHgnZ1ZprZRDvL0uIkcH?p=preview