AngularJS 中带 ng- 重复作用域的指令隔离作用域

我有一个带有隔离范围的指令(这样我就可以在其他地方重用该指令) ,当我将该指令与 ng-repeat一起使用时,它失败了。

我已经阅读了关于这个主题的所有文档和 Stack Overflow 答案,并理解了这些问题。我相信我已经避免了所有常见的陷阱。

所以我知道我的代码失败是因为 ng-repeat指令创建的范围。我自己的指令创建一个隔离作用域,并对父作用域中的对象执行双向数据绑定。我的指令将分配一个新的对象值给这个绑定的变量,当我的指令在没有 ng-repeat的情况下使用时(父变量被正确地更新) ,这种方法可以很好地工作。但是,对于 ng-repeat,赋值会在 ng-repeat范围内创建一个新变量,而父变量不会看到更改。根据我读到的资料,这一切都在意料之中。

我还读到,当给定元素上有多个指令时,只创建一个作用域。并且可以在每个指令中设置一个 priority来定义指令应用的顺序; 指令按优先级排序,然后调用它们的编译函数(在 http://docs.angularjs.org/guide/directive中搜索词优先级)。

因此,我希望能够使用优先级来确保我的指令首先运行,并最终创建一个隔离作用域,当 ng-repeat运行时,它重用隔离作用域,而不是创建一个通常从父作用域继承的作用域。ng-repeat文档指出,该指令在优先级 1000运行。目前尚不清楚 1是高优先级还是低优先级。当我在我的指令中使用优先级 1时,它没有产生任何影响,所以我尝试了 2000。但这使情况变得更糟: 我的双向绑定变成了 undefined,而我的指令没有显示任何东西。

我创建了 用小提琴来显示我的问题。我已经在我的指令中注释掉了 priority设置。我有一个名称对象列表和一个名为 name-row的指令,它显示了名称对象中的名字和姓氏字段。当单击显示的名称时,我希望它在主作用域中设置一个 selected变量。使用双向数据绑定将名称数组、 selected变量传递给 name-row指令。

我知道如何通过调用 main 作用域中的函数来实现这一点。我还知道,如果 selected在另一个对象内部,并且我绑定到外部对象,那么就可以工作了。但我目前对这些解决方案不感兴趣。

相反,我的问题是:

  • 如何防止 ng-repeat创建一个通常从父作用域继承的作用域,而是让它使用我的指令的隔离作用域?
  • 为什么我的指令中的优先级 2000不工作?
  • 使用 Batarang,是否可能知道正在使用的作用域类型?
55648 次浏览

Without directly trying to avoid answering your questions, instead take a look at the following fiddle:

http://jsfiddle.net/dVPLM/

Key point is that instead of trying to fight and change the conventional behaviour of Angular, you could structure your directive to work with ng-repeat as opposed to trying to override it.

In your template:

    <name-row
in-names-list="names"
io-selected="selected">
</name-row>

In your directive:

    template:
'        <ul>' +
'            <li ng-repeat="name in inNamesList" ng-class="activeClass($index)" >' +
'                <a ng-click="setSelected($index)">' +
'                    \{\{$index}} - \{\{name.first}} \{\{name.last}}' +
'                </a>' +
'            </li>' +
'        </ul>'

In response to your questions:

Okay, through a lot of the comments above, I have discovered the confusion. First, a couple of points of clarification:

  • ngRepeat does not affect your chosen isolate scope
  • the parameters passed into ngRepeat for use on your directive's attributes do use a prototypically-inherited scope
  • the reason your directive doesn't work has nothing to do with the isolate scope

Here's an example of the same code but with the directive removed:

<li ng-repeat="name in names"
ng-class="{ active: $index == selected }"
ng-click="selected = $index">
\{\{$index}}: \{\{name.first}} \{\{name.last}}
</li>

Here is a JSFiddle demonstrating that it won't work. You get the exact same results as in your directive.

Why doesn't it work? Because scopes in AngularJS use prototypical inheritance. The value selected on your parent scope is a primitive. In JavaScript, this means that it will be overwritten when a child sets the same value. There is a golden rule in AngularJS scopes: model values should always have a . in them. That is, they should never be primitives. See this SO answer for more information.


Here is a picture of what the scopes initially look like.

enter image description here

After clicking the first item, the scopes now look like this:

enter image description here

Notice that a new selected property was created on the ngRepeat scope. The controller scope 003 was not altered.

You can probably guess what happens when we click on the second item:

enter image description here


So your issue is actually not caused by ngRepeat at all - it's caused by breaking a golden rule in AngularJS. The way to fix it is to simply use an object property:

$scope.state = { selected: undefined };
<li ng-repeat="name in names"
ng-class="{ active: $index == state.selected }"
ng-click="state.selected = $index">
\{\{$index}}: \{\{name.first}} \{\{name.last}}
</li>

Here is a second JSFiddle showing this works too.

Here is what the scopes look like initially:

enter image description here

After clicking the first item:

enter image description here

Here, the controller scope is being affected, as desired.

Also, to prove that this will still work with your directive with an isolate scope (because, again, this has nothing to do with your problem), here is a JSFiddle for that too, the view must reflect the object. You'll note that the only necessary change was to use an object instead of a primitive.

Scopes initially:

enter image description here

Scopes after clicking on the first item:

enter image description here

To conclude: once again, your issue isn't with the isolate scope and it isn't with how ngRepeat works. Your problem is that you're breaking a rule that is known to lead to this very problem. Models in AngularJS should always have a ..