【问题标题】:Accessing parent scope in directive when using controllerAs使用 controllerAs 时在指令中访问父作用域
【发布时间】:2015-09-15 03:43:45
【问题描述】:

我目前有一个使用父控制器范围内的属性的指令:

.controller('MainCtrl', function($scope) {
  $scope.name = 'My Name';
})

.directive('myDirective', function() {
  return {
    scope: true,
    controller: function($scope) {
      console.log($scope.name); // logs 'My Name'
    }
  };
})

现在我在控制器中使用controllerAs 语法,但我不知道如何在指令的控制器中获取对控制器对象的引用。

.controller('MainCtrl', function() {
  var vm = this;
  vm.name = 'My Name';
})

.directive('myDirective', function() {
  return {
    scope: true,
    controller: function($scope) {
      console.log(vm.name); // logs 'Undefined'
    }
  };
})

a plunkr 说明了这个问题。

我还发现 this article 试图解释类似的东西,但在这种情况下,他只是重复使用完全相同的控制器。

【问题讨论】:

    标签: javascript angularjs angularjs-directive angularjs-scope


    【解决方案1】:

    当您使用 ControllerAs 语法时,会在 $scope 对象上创建一个属性,该对象是您的控制器的别名。例如,ng-controller="MainCtrl as vm" 为您提供$scope.vm$scope 隐含在 HTML 中,因此在 HTML 中访问 vm.name 与在 JavaScript 中访问 $scope.vm.name 相同。

    在控制器中,您可以访问this.name$scope.vm.name,它们在功能上是等效的。但是,在其他控制器中,this 将引用该特定控制器,因此this.name 将不起作用。

    因此,在这种情况下,您可以使用 $scope.vm.name 在指令的控制器中访问所需的属性。 http://plnkr.co/edit/WTJy7LlB7VRJzwTGdFYs?p=preview

    但是,您可能还希望将 ControllerAs 语法与指令一起使用;在这种情况下,我建议不要使用vm 作为控制器名称,而是使用唯一的名称来帮助识别您所指的控制器。 MainCtrl as main,然后引用main.name会更清楚。

    但是,如果可能,我建议使用隔离作用域,因为它可以让您完全消除将$scope 注入指令的需要,并允许您的指令是自包含且可重用的。

    旁注,bindToController: true, 如果您不使用隔离作用域,则不会执行任何操作;当您正在使用隔离范围时,它会在隔离控制器上创建属性以匹配传入的范围,从而允许您访问传入的值而无需$scope

    【讨论】:

    • 这正是我所需要的——谢谢。我没有想到 html 中 vm 的关系真的是 $scope.vm,这意味着我仍然可以访问更下游的属性。我也喜欢关于隔离作用域的建议——我认为隔离作用域只会用于防止交互,而不是一种更明确的方式。
    • 要在带有 ControllerAs 的指令中使用我的父控制器,我可以使用 $scope.vm...但是我在我的指令中对 vm 进行了编码...如果我想使用它也在其他地方..我需要确保我的父控制器的别名是 vm..否则它将无法工作..有什么解决方法..?
    • @rishitiwari 一般来说,您应该努力制作不依赖于其父主机的不可知指令。而不是访问父控制器,你应该接受你的指令将操作的数据作为参数
    • @Claies:这是否意味着只有在使用 ControllerAs 来使指令通用时才应该使用隔离范围?
    • 我的建议是总是对指令使用独立的作用域,真的很少有理由不这样做。但是,如果出于某种原因必须使用继承范围,则可以使用 $parent 来引用父控制器,但这可能会很麻烦,尤其是如果您开始使用 $parent.$parent.$parent.....
    【解决方案2】:

    一种选择是遍历$scope 链,直到找到 vm。

    app.directive('myDir', function() {
      return {
        restrict: 'E',
        scope: true,
        template: '<div>my directive</div>',
        bindToController: true,
        controller: function($scope) {
          console.log($scope.name2); // logs 'bound to the controller scope'
          console.log($scope.$parent.vm.name); // logs 'bound to the controller vm'
        }
      };  
    });
    

    但是,这可能真的很脆,而且闻起来有点臭。

    更复杂和深思熟虑的方法是通过传递的参数将控制器范围的属性绑定到指令。

    HTML:

    <my-dir name="vm.name" />
    

    JS:

    app.directive('myDir', function() {
      return {
        restrict: 'E',
        scope: {
          name: "="
        },
        template: '<div>my directive</div>',
        bindToController: true,
        controller: function($scope) {
          console.log($scope.name); // logs 'bound to the controller vm' 
        }
      };  
    });
    

    plunkr

    【讨论】:

    • 所以这确实有效,尽管有点令人沮丧的是外部vm 上的每个属性都需要通过属性手动绑定到指令,这可能会变得混乱。我不明白的是为什么我必须使用隔离范围,继承的范围仍然应该让我访问该属性。
    • 如果您愿意,您当然可以传递整个范围而不是单个属性。这将完成您所需要的,尽管我会小心地将控制器中的大量内容暴露给您的指令。过多的暴露会强烈地耦合您的组件并降低它们的可重用性(这是当前对 Scope/vm/Angular 1.x 的抱怨之一,也是我们将在 Angular 2 中看到变化最大的事情之一)。
    • 您可以选择仅传递alias 属性,而不是传递整个范围。这样,您仍然可以使用 $scope 在原始范围内定义事物,并且这些属性不会反映在指令中
    • @SaurabhTiwari 即使你传递了 scope 的别名而不是 $scope,你仍然会暴露整个层次结构。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-08
    • 2016-03-13
    • 1970-01-01
    • 2015-11-07
    • 2016-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多