如何在AngularJS中的自定义指令*中使用自己的作用域*访问父作用域?


327

我正在寻找访问指令中“父”范围的任何方式。范围,超越,要求,从上方传入变量(或范围本身)的任何组合,等等。我完全愿意向后弯腰,但我想避免某些完全不可靠或无法维护的事情。例如,我知道我现在可以通过$scope从preLink参数中获取并对其$sibling范围进行迭代以找到概念上的“父级” 来做到这一点。

我真正想要的是能够$watch在父作用域中表达。如果我可以做到,那么我可以在这里完成我想做的事情: AngularJS-如何用变量渲染局部变量?

重要说明是,该指令必须在同一父范围内可重复使用。因此,默认行为(作用域:false)对我不起作用。我需要为指令的每个实例设置一个单独的作用域,然后需要$watch一个位于父作用域中的变量。

一个代码示例价值1000个字,因此:

app.directive('watchingMyParentScope', function() {
    return {
        require: /* ? */,
        scope: /* ? */,
        transclude: /* ? */,
        controller: /* ? */,
        compile: function(el,attr,trans) {
            // Can I get the $parent from the transclusion function somehow?
            return {
                pre: function($s, $e, $a, parentControl) {
                    // Can I get the $parent from the parent controller?
                    // By setting this.$scope = $scope from within that controller?

                    // Can I get the $parent from the current $scope?

                    // Can I pass the $parent scope in as an attribute and define
                    // it as part of this directive's scope definition?

                    // What don't I understand about how directives work and
                    // how their scope is related to their parent?
                },
                post: function($s, $e, $a, parentControl) {
                    // Has my situation improved by the time the postLink is called?
                }
            }
        }
    };
});

Answers:


644

请参阅AngularJS中范围原型/原型继承的细微差别?

总结一下:指令访问其parent($parent)范围的方式取决于该指令创建的范围的类型:

  1. default(scope: false)-指令不会创建新的作用域,因此此处没有继承。指令的作用域与父/容器的作用域相同。在链接功能中,使用第一个参数(通常为scope)。

  2. scope: true-伪指令创建了一个新的子范围,该子范围从原型上继承自父范围。在父作用域上定义的属性可用于该指令scope(因为是原型继承)。只是要谨防写入原始范围属性-会在指令范围内创建一个新属性(隐藏/阴影同名的父范围属性)。

  3. scope: { ... }-指令创建了一个新的隔离/隔离范围。它不原型继承父作用域。您仍然可以使用来访问父范围$parent,但是通常不建议这样做。相反,应指定的父范围属性(和/或功能)经由相同的元件上的附加属性,在使用该指令,利用该指令需要=@&表示法。

  4. transclude: true-伪指令创建一个新的“已包含”子作用域,该子作用域通常从父作用域继承。如果该指令还创建了一个隔离范围,那么被包含的和隔离范围就是同级。$parent每个范围的属性引用相同的父范围。
    Angular v1.3更新:如果该指令还创建了一个隔离范围,那么被包含的范围现在是该隔离范围的子级。超越范围和孤立范围不再是同级。现在$parent,已包含范围的属性引用了隔离范围。

上面的链接提供了所有4种类型的示例和图片。

您不能在指令的编译函数中访问范围(如此处所述:https : //github.com/angular/angular.js/wiki/Understanding-Directives)。您可以在链接函数中访问指令的作用域。

观看:

对于上述1.和2 .:通常,您可以通过属性指定指令需要哪个父属性,然后$ watch:

<div my-dir attr1="prop1"></div>

scope.$watch(attrs.attr1, function() { ... });

如果您正在查看对象属性,则需要使用$ parse:

<div my-dir attr2="obj.prop2"></div>

var model = $parse(attrs.attr2);
scope.$watch(model, function() { ... });

对于以上3.(隔离范围),请使用@=表示法观察为指令属性指定的名称:

<div my-dir attr3="{{prop3}}" attr4="obj.prop4"></div>

scope: {
  localName3: '@attr3',
  attr4:      '='  // here, using the same name as the attribute
},
link: function(scope, element, attrs) {
   scope.$watch('localName3', function() { ... });
   scope.$watch('attr4',      function() { ... });

1
谢谢马克。事实证明,我在“ 如何使用变量渲染局部零件”上发布的解决方案确实非常漂亮。您真正需要链接到我的东西是“编写HTML并意识到您的元素没有嵌套在您认为是的ng-controller中的细微差别”。哇...菜鸟错误。但这是对其他解释范围的答案(更长)的有用补充。
colllin

@collin,太好了,很高兴您解决了您的问题,因为我不太确定如何回应您的其他评论(现已删除)。
Mark Rajcok

我可以/应该执行哪些操作scope.$watch('localName3', function() { ...[?? WHAT TO DO HERE for example?] });
Junaid Qadir 2014年

1
@Andy,不要$parse=fiddle一起使用。 $parse只有非隔离范围才需要。
Mark Rajcok'1

1
这是一个很好的答案,非常彻底。这也说明了为什么我只是讨厌使用AngularJS。
John Trichereau

51

访问控制器方法意味着从指令controller / link / scope访问父作用域上的方法。

如果指令正在共享/继承父作用域,那么直接调用父作用域方法就很简单了。

当您要从Isolated指令范围访问父范围方法时,只需要进行很少的工作。

从隔离的指令范围调用父范围方法或观察父范围变量(特别是选项6)的选项很少(可能比下面列出的更多)。

请注意,我link function在这些示例中使用过directive controller,但是您也可以根据需要使用。

选项1。 通过对象文字和从指令html模板

index.html

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <p>Hello {{name}}!</p>

    <p> Directive Content</p>
    <sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged(selectedItems)" items="items"> </sd-items-filter>


    <P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}} </p>

  </body>

</html>

itemfilterTemplate.html

<select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" ng-change="selectedItemsChanged({selectedItems:selectedItems})" ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
  <option>--</option>
</select>

app.js

var app = angular.module('plunker', []);

app.directive('sdItemsFilter', function() {
  return {
    restrict: 'E',
    scope: {
      items: '=',
      selectedItems: '=',
      selectedItemsChanged: '&'
    },
    templateUrl: "itemfilterTemplate.html"
  }
})

app.controller('MainCtrl', function($scope) {
  $scope.name = 'TARS';

  $scope.selectedItems = ["allItems"];

  $scope.selectedItemsChanged = function(selectedItems1) {
    $scope.selectedItemsReturnedFromDirective = selectedItems1;
  }

  $scope.items = [{
    "id": "allItems",
    "name": "All Items",
    "order": 0
  }, {
    "id": "CaseItem",
    "name": "Case Item",
    "model": "PredefinedModel"
  }, {
    "id": "Application",
    "name": "Application",
    "model": "Bank"
    }]

});

工作的plnkr:http ://plnkr.co/edit/rgKUsYGDo9O3tewL6xgr?p=preview

选项#2。通过对象文字和从指令链接/作用域

index.html

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <p>Hello {{name}}!</p>

    <p> Directive Content</p>
    <sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged(selectedItems)" items="items"> </sd-items-filter>


    <P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}} </p>

  </body>

</html>

itemfilterTemplate.html

<select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" 
 ng-change="selectedItemsChangedDir()" ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
  <option>--</option>
</select>

app.js

var app = angular.module('plunker', []);

app.directive('sdItemsFilter', function() {
  return {
    restrict: 'E',
    scope: {
      items: '=',
      selectedItems: '=',
      selectedItemsChanged: '&'
    },
    templateUrl: "itemfilterTemplate.html",
    link: function (scope, element, attrs){
      scope.selectedItemsChangedDir = function(){
        scope.selectedItemsChanged({selectedItems:scope.selectedItems});  
      }
    }
  }
})

app.controller('MainCtrl', function($scope) {
  $scope.name = 'TARS';

  $scope.selectedItems = ["allItems"];

  $scope.selectedItemsChanged = function(selectedItems1) {
    $scope.selectedItemsReturnedFromDirective = selectedItems1;
  }

  $scope.items = [{
    "id": "allItems",
    "name": "All Items",
    "order": 0
  }, {
    "id": "CaseItem",
    "name": "Case Item",
    "model": "PredefinedModel"
  }, {
    "id": "Application",
    "name": "Application",
    "model": "Bank"
    }]
});

工作的plnkr:http ://plnkr.co/edit/BRvYm2SpSpBK9uxNIcTa?p=preview

选项#3。通过函数引用和指令HTML模板

index.html

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <p>Hello {{name}}!</p>

    <p> Directive Content</p>
    <sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged" items="items"> </sd-items-filter>


    <P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnFromDirective}} </p>

  </body>

</html>

itemfilterTemplate.html

<select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" 
 ng-change="selectedItemsChanged()(selectedItems)" ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
  <option>--</option>
</select>

app.js

var app = angular.module('plunker', []);

app.directive('sdItemsFilter', function() {
  return {
    restrict: 'E',
    scope: {
      items: '=',
      selectedItems:'=',
      selectedItemsChanged: '&'
    },
    templateUrl: "itemfilterTemplate.html"
  }
})

app.controller('MainCtrl', function($scope) {
  $scope.name = 'TARS';

  $scope.selectedItems = ["allItems"];

  $scope.selectedItemsChanged = function(selectedItems1) {
    $scope.selectedItemsReturnFromDirective = selectedItems1;
  }

  $scope.items = [{
    "id": "allItems",
    "name": "All Items",
    "order": 0
  }, {
    "id": "CaseItem",
    "name": "Case Item",
    "model": "PredefinedModel"
  }, {
    "id": "Application",
    "name": "Application",
    "model": "Bank"
    }]
});

工作的plnkr:http ://plnkr.co/edit/Jo6FcYfVXCCg3vH42BIz?p=preview

选项#4。通过函数引用和来自指令链接/作用域

index.html

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <p>Hello {{name}}!</p>

    <p> Directive Content</p>
    <sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged" items="items"> </sd-items-filter>


    <P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}} </p>

  </body>

</html>

itemfilterTemplate.html

<select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" ng-change="selectedItemsChangedDir()" ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
  <option>--</option>
</select>

app.js

var app = angular.module('plunker', []);

app.directive('sdItemsFilter', function() {
  return {
    restrict: 'E',
    scope: {
      items: '=',
      selectedItems: '=',
      selectedItemsChanged: '&'
    },
    templateUrl: "itemfilterTemplate.html",
    link: function (scope, element, attrs){
      scope.selectedItemsChangedDir = function(){
        scope.selectedItemsChanged()(scope.selectedItems);  
      }
    }
  }
})

app.controller('MainCtrl', function($scope) {
  $scope.name = 'TARS';

  $scope.selectedItems = ["allItems"];

  $scope.selectedItemsChanged = function(selectedItems1) {
    $scope.selectedItemsReturnedFromDirective = selectedItems1;
  }

  $scope.items = [{
    "id": "allItems",
    "name": "All Items",
    "order": 0
  }, {
    "id": "CaseItem",
    "name": "Case Item",
    "model": "PredefinedModel"
  }, {
    "id": "Application",
    "name": "Application",
    "model": "Bank"
    }]

});

工作的plnkr:http ://plnkr.co/edit/BSqx2J1yCY86IJwAnQF1?p=preview

选项5:通过ng-model和双向绑定,您可以更新父范围变量。。因此,在某些情况下,您可能不需要调用父范围函数。

index.html

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <p>Hello {{name}}!</p>

    <p> Directive Content</p>
    <sd-items-filter ng-model="selectedItems" selected-items-changed="selectedItemsChanged" items="items"> </sd-items-filter>


    <P style="color:red">Selected Items (in parent controller) set to: {{selectedItems}} </p>

  </body>

</html>

itemfilterTemplate.html

<select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" 
 ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
  <option>--</option>
</select>

app.js

var app = angular.module('plunker', []);

app.directive('sdItemsFilter', function() {
  return {
    restrict: 'E',
    scope: {
      items: '=',
      selectedItems: '=ngModel'
    },
    templateUrl: "itemfilterTemplate.html"
  }
})

app.controller('MainCtrl', function($scope) {
  $scope.name = 'TARS';

  $scope.selectedItems = ["allItems"];

  $scope.items = [{
    "id": "allItems",
    "name": "All Items",
    "order": 0
  }, {
    "id": "CaseItem",
    "name": "Case Item",
    "model": "PredefinedModel"
  }, {
    "id": "Application",
    "name": "Application",
    "model": "Bank"
    }]
});

工作的plnkr:http ://plnkr.co/edit/hNui3xgzdTnfcdzljihY?p=preview

选项#6:$watch$watchCollectionitems在上述所有示例中,“ 通过” 和“ 绑定”是两种方式绑定,如果在父作用域中修改了项目,则伪指令中的项目也会反映所做的更改。

如果要从父范围观察其他属性或对象,则可以使用$watch$watchCollection如下所示

html

<!DOCTYPE html>
<html ng-app="plunker">

<head>
  <meta charset="utf-8" />
  <title>AngularJS Plunker</title>
  <script>
    document.write('<base href="' + document.location + '" />');
  </script>
  <link rel="stylesheet" href="style.css" />
  <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
  <script src="app.js"></script>
</head>

<body ng-controller="MainCtrl">
  <p>Hello {{user}}!</p>
  <p>directive is watching name and current item</p>
  <table>
    <tr>
      <td>Id:</td>
      <td>
        <input type="text" ng-model="id" />
      </td>
    </tr>
    <tr>
      <td>Name:</td>
      <td>
        <input type="text" ng-model="name" />
      </td>
    </tr>
    <tr>
      <td>Model:</td>
      <td>
        <input type="text" ng-model="model" />
      </td>
    </tr>
  </table>

  <button style="margin-left:50px" type="buttun" ng-click="addItem()">Add Item</button>

  <p>Directive Contents</p>
  <sd-items-filter ng-model="selectedItems" current-item="currentItem" name="{{name}}" selected-items-changed="selectedItemsChanged" items="items"></sd-items-filter>

  <P style="color:red">Selected Items (in parent controller) set to: {{selectedItems}}</p>
</body>

</html>

脚本app.js

var app = angular.module('plunker',[]);

app.directive('sdItemsFilter', function() {
  return {
    restrict: 'E',
    scope: {
      name: '@',
      currentItem: '=',
      items: '=',
      selectedItems: '=ngModel'
    },
    template: '<select ng-model="selectedItems" multiple="multiple" style="height: 140px; width: 250px;"' +
      'ng-options="item.id as item.name group by item.model for item in items | orderBy:\'name\'">' +
      '<option>--</option> </select>',
    link: function(scope, element, attrs) {
      scope.$watchCollection('currentItem', function() {
        console.log(JSON.stringify(scope.currentItem));
      });
      scope.$watch('name', function() {
        console.log(JSON.stringify(scope.name));
      });
    }
  }
})

 app.controller('MainCtrl', function($scope) {
  $scope.user = 'World';

  $scope.addItem = function() {
    $scope.items.push({
      id: $scope.id,
      name: $scope.name,
      model: $scope.model
    });
    $scope.currentItem = {};
    $scope.currentItem.id = $scope.id;
    $scope.currentItem.name = $scope.name;
    $scope.currentItem.model = $scope.model;
  }

  $scope.selectedItems = ["allItems"];

  $scope.items = [{
    "id": "allItems",
    "name": "All Items",
    "order": 0
  }, {
    "id": "CaseItem",
    "name": "Case Item",
    "model": "PredefinedModel"
  }, {
    "id": "Application",
    "name": "Application",
    "model": "Bank"
  }]
});

您始终可以参考AngularJs文档以获取有关指令的详细说明。


10
他为自己的代表而努力...为他的代表而努力...他为他的代表而努力,因此您最好正确地投票给他。
2016年

7
否决-答案之内的任何有价值的信息由于篇幅太短而无法访问
纠正

2
我用清晰分隔的所有可用替代方法回答了这个问题。在我看来,简短的回答并不总是有用的,除非您对自己有一个全面的了解。
Yogesh Manware'3

@YogeshManware:可以通过省略不相关的东西(例如样式表),不使用冗长的标记,简化示例以不使用“ group by”之类的东西等方式将其大大缩短。对于某些解释,它也非常有用每个例子。
该死的

这不是拒绝投票的理由。人们滥用此特权
Winnemucca

11
 scope: false
 transclude: false

并且您将具有相同的范围(带有父元素)

$scope.$watch(...

根据这两个选项scope&transclude,有很多方法可以访问父范围。


是的,简短而甜美,正确。它们似乎与父元素共享完全相同的作用域……这使得它们无法在同一作用域中重复使用。 jsfiddle.net/collindo/xqytH
colllin

2
很多时候我们在编写可重用组件时都需要隔离作用域,因此解决方案不是那么简单
Yvon Huynh 2016年

8

这是我曾经使用的一个技巧:创建一个“虚拟”指令来保存父作用域并将其放置在所需指令之外的某个位置。就像是:

module.directive('myDirectiveContainer', function () {
    return {
        controller: function ($scope) {
            this.scope = $scope;
        }
    };
});

module.directive('myDirective', function () {
    return {
        require: '^myDirectiveContainer',
        link: function (scope, element, attrs, containerController) {
            // use containerController.scope here...
        }
    };
});

然后

<div my-directive-container="">
    <div my-directive="">
    </div>
</div>

也许不是最优雅的解决方案,但它完成了工作。


4

如果您使用的是ES6类和ControllerAs语法,则需要做一些稍微不同的事情。

请参见下面的代码段,并注意这vmControllerAs父HTML中使用的父Controller 的值

myApp.directive('name', function() {
  return {
    // no scope definition
    link : function(scope, element, attrs, ngModel) {

        scope.vm.func(...)

0

经过一切尝试,我终于想出了解决方案。

只需将以下内容放在模板中:

{{currentDirective.attr = parentDirective.attr; ''}}

它只是编写要访问当前作用域的父作用域属性/变量。

还要注意; ''语句末尾的,这是为了确保模板中没有输出。(Angular计算每条语句,但仅输出最后一条)。

它有点笨拙,但经过数小时的反复试验,它才能完成工作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.