【问题标题】:Accessing $scope From a Directive in AngularJS从 AngularJS 中的指令访问 $scope
【发布时间】:2017-08-15 08:23:59
【问题描述】:

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

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

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

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

指令一:

//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:如果用户将鼠标悬停在 上,指令中的过滤器将返回此 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 链接中演示的 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 工具提示将包含调用 Web API 更新功能的 html 按钮。

我们在这里有一个按比例缩小的 POC Plunk:

POC Plunk

还在考虑将 Angular Bootstrap UI 用于工具:

Bootstrap UI Plunk

希望这是有道理的。

【问题讨论】:

  • 我不确定我是否理解您想要做什么。基本上,您想找到一种方法来跨指令共享 scope 变量值,对吗?
  • 从我对 Angular 的有限理解来看,我认为这是正确的。在第二个指令中,我们正在考虑是否可以根据用户悬停在的项目访问或设置 div 标签中的值,我们可以创建一个 div toollip 来显​​示给用户。我们目前不知道如何做到这一点。我希望这能回答你的问题。谢谢
  • 是的,我认为这是有道理的。因此,您的代码遇到的第一个问题是您尝试使用$scope,但没有在任何地方注入它——尽管您确实有scope。除非您想使用隔离范围获得超级花哨,否则如果您想在指令之间传递值,您可以查看将它们绑定到 $rootScope 或使用工厂或服务传递它们。老实说,如果你们对 Angular 的经验有限,那就让它超级简单。不要试图完美地做每一件事,而是让它发挥作用,当你理解它时再回来。
  • 感谢您的回复。你有一个例子说明我们如何在当前问题的背景下做到这一点?尝试将作用域注入:angular.module("FFPA").directive('cubehvr', ['$scope', '$compile', 'dataService', function ($scope, $compile, dataService) {}。错误:错误:[$injector:unpr] 未知提供者:$scopeProvider
  • 是的,如果使用$scope 是您想要做的事情,我想知道。根据您想要执行的操作,您可以为两个指令声明相同的 controller 并使用它来访问共享范围。老实说,我仍然不是 100% 清楚你想要什么。话虽如此,为了传递数据,我会考虑使用隔离范围或仅使用服务/工厂在指令之间传递值。

标签: javascript angularjs svg angularjs-directive angularjs-scope


【解决方案1】:

//编辑。我再次阅读了您的问题,并通过了我的回答。我没有完全回答你的问题,因为它是多层次的。现在我将解决您的所有问题并尝试回答:

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

$scope 是 MVVM 设计模式中的模型视图,它将您的模板(视图)和模型粘合在一起。从理论上讲,您可能可以将 $scope 传递给另一个指令,但我认为这是一种反模式。

  1. 指令之间的通信。您可以使用至少 4 种方法来传达指令:

    • 共享相同的范围,你几乎在你的 plunker 中做了什么,只是不要在你的指令规范中定义任何“范围”。我不确定这是否是最好的方法,因为您的任何指令都可能使您的范围数据失真。
    • 创建隔离作用域并使用 ng-model 或 $watch,这是更安全的方法,但需要更多开销。在这种情况下,您将变量向下传递,您的作用域是.$watch。这是双向绑定。您可以推送和拉取值。 $watch
    • 创建服务,您可以在其中保留事件总线或变量存储等内容
    • 您可以将指令与事件通信: $on $emit 这适用于分层指令(如此有效,您必须创建独立的子范围)
  2. 为 SVG 的子元素添加弹出框。 Bootstrap 能够将弹出框添加到正文而不是父元素。它对 SVG 很有用:https://angular-ui.github.io/bootstrap/#!#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 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

【讨论】:

  • 谢谢。悬停似乎只显示一个人的名字(Robin Zander),但有四个人,每个立方体中有一个人。 1)。 Bun E. 卡洛斯 2)。里克尼尔森 3)。罗宾·赞德 4)。汤姆·彼得森。
  • 是的,因为在我发送给您的 plunker 中,它仅使用第一个条目:thisData[0],您可以循环或将整个集合传递给您的范围。只需这样做:scope.name.set = thisData; (这是 script.js 中的第 71 行)
  • 我认为还有其他问题。在我们之前的版本中,当 scope:true: plnkr.co/edit/bzYjzxLZGBGDYoDZK2xl?p=preview 时,我们能够识别用户悬停在上面的多维数据集 id 但是在删除了范围的新指令中,指令总是将多维数据集视为 f3s362c1
  • 我更新了代码以返回正确的值。考虑将悬停处理程序移动到指令的上层并跟踪您悬停在哪个元素上。 jQuery 委托(或具有 3 个参数的“开启”功能)所做的事情。您可以将 $event 参数传递给模板中的 onHover 函数:而不是 "c​​ubeHover('" + elementId + "')" do "cubeHover($event)"
  • 谢谢奥斯卡。我们之所以选择 Angular,是因为它被推荐为使用 SVG 的解决方案,并且在 Javascript/Jquery 作为 HTML DOM 的 API 方面有经验,我们认为它相当容易上手,但很快发现这是一个错误的假设. Angular 的学习曲线似乎相当陡峭。再次感谢您的帮助。
猜你喜欢
  • 2014-07-09
  • 1970-01-01
  • 1970-01-01
  • 2012-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-26
  • 2013-10-17
相关资源
最近更新 更多