双向绑定在具有嵌入范围的指令中不起作用 [英] Two way binding not working in directive with transcluded scope

查看:26
本文介绍了双向绑定在具有嵌入范围的指令中不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在控制器中有一个文本框,它绑定到模型 name.控制器内有一个指令,指令内还有另一个文本框,它绑定到相同的模型 name:

控制器:editCtrl <br/><input type="text" ng-model="name"/><br/><标签>指令:标签 <br/><input type="text" ng-model="name"/></tabs>

mod.directive('tabs', function() {返回 {限制:'E',转置:真实,模板:'<div class="border" ng-transclude></div>',};});

当您在外部文本框中输入内容时,它会反映在内部文本框中,但如果您在内部文本框中输入内容,它就会停止工作,即两个文本框不再反映相同的值.

参见示例:,您可以在其中检查两个范围.在输入第二个文本框之前,单击第二个文本框旁边的显示范围"链接.这将允许您查看嵌入的子范围.您会注意到此时它没有 name 属性.清除控制台,在第二个文本框中键入,然后再次单击该链接.您会注意到子作用域现在有一个 name 属性,初始值是父属性的值(Mark").如果你在第二个文本框中输入likes Angular",作用域是这样的:

有两种解决方案:

  1. 按照@pgreen2 的建议进行操作(这是最佳实践"解决方案)——使用对象而不是原语.使用对象时,子/嵌入范围不会获得新属性.这里只有原型继承在起作用.在下图中,假设 editCtrl 的 $scope 定义了这个对象:
    $scope.myObject = { name: "Mark", anotherProp: ... }:
  2. 在子作用域中使用 $parent(这是一个脆弱的解决方案,不推荐,因为它对 HTML 结构做出假设):在子作用域中使用 ng-model="$parent.name" 位于 元素内.上面的第一张图片显示了这是如何工作的.

使用 scope: {name: '='} 时出现语法错误,因为使用双向数据绑定时(即使用 '=' 时),不允许插值 -- 即,{{}} 不能使用.使用 代替 .

使用 '@' 与 transclude 情况相同,因为 ng-transclude 使用的是 transcluded 作用域,而不是使用 scope: { ... } 创建的隔离作用域.

有关范围(包括图片)的(大量)更多信息,请参阅
AngularJS 中范围原型/原型继承的细微差别是什么?

I've a textbox in a controller which is bound to model name. There's a directive inside the controller and there's another textbox inside the directive which is bound to the same model name:

<div class="border" ng-controller="editCtrl">
   Controller: editCtrl <br/>
   <input type="text" ng-model="name" />
   <br/>
   <tabs>
      Directive: tabs <br/>
      <input type="text" ng-model="name"/>
   </tabs>
</div>

mod.directive('tabs', function() {
  return {
    restrict: 'E',
    transclude: true, 
    template:
      '<div class="border" ng-transclude></div>',
  };
});

When you type something in the outer textbox it's reflected in the inner textbox but if you type something in the inner textbox it stops working i.e. both textbox no more reflects the same value.

See example at: http://jsfiddle.net/uzairfarooq/MNBLd/

I've also tried using two way binding attr (scope: {name: '='}) but it gives syntax error.And using scope: {name: '@'} has same effect.

Any help would be greatly appreciated.

In addition to the accepted answer, this article really helped me in understanding the prototypical inheritance in child scpoes. I'd highly recommend anyone having problem with scopes to read it thoroughly.

解决方案

A directive with transclude: true results in the directive creating a new (transcluded) child scope. This new scope prototypically inherits from the parent scope. In your case, the parent scope is the scope associated with the editCtrl controller.

Using two-way databinding in a child scope (i.e., ng-model) to bind to a parent scope property that holds a primitive value (e.g., name) always causes problems -- well, I should say that it doesn't work as expected. When the scope property is changed in the child (e.g., you type into the second textbox) the child creates a new scope property that hides/shadows the parent scope property of the same name. If the parent property holds a primitive value, that value is (essentially) copied to the child property when the child property is created. Future changes in the child scope (e.g., the second textbox) only affect the child property.

Before typing into the second textbox (i.e., before the property is changed in the child), the child/transcluded scope finds the name property in the parent scope via prototypal inheritance (dashed line in picture below). This is why the two textboxes initially remain in synch. Below, if you type "Mark" into the first text box, this is what the scopes look like:

I created a fiddle where you can examine the two scopes. Click the "show scope" link next to the second textbox before typing into the second textbox. This will allow you to see the transcluded child scope. You will notice that it does not have a name property at this point. Clear the console, type into the second text box, then click the link again. You will notice that the child scope now has a name property, and the initial value was the value that parent property had ("Mark"). If you typed " likes Angular" into the second text box, this is what the scopes look like:

There are two solutions:

  1. do what @pgreen2 suggests (this is the "best practice" solution) -- use an object instead of a primitive. When an object is used, the child/transcluded scope does not get a new property. Only prototypal inheritance is in play here. In the picture below, assume the editCtrl's $scope has this object defined:
    $scope.myObject = { name: "Mark", anotherProp: ... }:
  2. use $parent in the child scope (this is a fragile solution, and not recommended, as it makes assumptions about HTML structure): use ng-model="$parent.name" inside the <input> that is within the <tabs> element. The first picture above shows how this works.

A syntax error occurs when using scope: {name: '='} because when using two-way databinding (i.e., when using '='), interpolation is not allowed -- i.e., {{}} can't be used. Instead of <tabs name="{{name}}"> use <tabs name="name">.

Using '@' works the same as the transclude case because ng-transclude uses the transcluded scope, not the isolate scope that is created by using scope: { ... }.

For (lots) more information about scopes (including pictures) see
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

这篇关于双向绑定在具有嵌入范围的指令中不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆